From c687dcd56fce134a2f20a84adadd49ee26c47417 Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Tue, 23 May 2023 15:33:19 -0700 Subject: [PATCH] Migrates android semanitcs integration to integration test (#127128) I think the flake is due to setclipboard or semantics update race condition. I migrated the test to use integration test package which relies less on timing fixes https://github.com/flutter/flutter/issues/124636 --- dev/devicelab/lib/framework/talkback.dart | 79 ++ .../lib/tasks/integration_tests.dart | 19 +- .../platforminteraction/MainActivity.java | 52 +- .../android/project-app.lockfile | 33 +- .../android/project-integration_test.lockfile | 122 +++ .../integration_test/main_test.dart | 677 +++++++++++++++++ .../android_semantics_testing/lib/main.dart | 46 -- .../lib/src/constants.dart | 1 + .../lib/src/matcher.dart | 12 +- .../android_semantics_testing/pubspec.yaml | 2 +- .../test_driver/main_test.dart | 702 ------------------ 11 files changed, 979 insertions(+), 766 deletions(-) create mode 100644 dev/devicelab/lib/framework/talkback.dart create mode 100644 dev/integration_tests/android_semantics_testing/android/project-integration_test.lockfile create mode 100644 dev/integration_tests/android_semantics_testing/integration_test/main_test.dart delete mode 100644 dev/integration_tests/android_semantics_testing/test_driver/main_test.dart diff --git a/dev/devicelab/lib/framework/talkback.dart b/dev/devicelab/lib/framework/talkback.dart new file mode 100644 index 0000000000..2edfa9eaaa --- /dev/null +++ b/dev/devicelab/lib/framework/talkback.dart @@ -0,0 +1,79 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io' as io; + +import 'package:path/path.dart' as path; +import 'package:pub_semver/pub_semver.dart'; + +String adbPath() { + final String? androidHome = io.Platform.environment['ANDROID_HOME'] ?? io.Platform.environment['ANDROID_SDK_ROOT']; + if (androidHome == null) { + return 'adb'; + } else { + return path.join(androidHome, 'platform-tools', 'adb'); + } +} + +Future getTalkbackVersion() async { + final io.ProcessResult result = await io.Process.run(adbPath(), const [ + 'shell', + 'dumpsys', + 'package', + 'com.google.android.marvin.talkback', + ]); + if (result.exitCode != 0) { + throw Exception('Failed to get TalkBack version: ${result.stdout as String}\n${result.stderr as String}'); + } + final List lines = (result.stdout as String).split('\n'); + String? version; + for (final String line in lines) { + if (line.contains('versionName')) { + version = line.replaceAll(RegExp(r'\s*versionName='), ''); + break; + } + } + if (version == null) { + throw Exception('Unable to determine TalkBack version.'); + } + + // Android doesn't quite use semver, so convert the version string to semver form. + final RegExp startVersion = RegExp(r'(?\d+)\.(?\d+)\.(?\d+)(\.(?\d+))?'); + final RegExpMatch? match = startVersion.firstMatch(version); + if (match == null) { + return Version(0, 0, 0); + } + return Version( + int.parse(match.namedGroup('major')!), + int.parse(match.namedGroup('minor')!), + int.parse(match.namedGroup('patch')!), + build: match.namedGroup('build'), + ); +} + +Future enableTalkBack() async { + final io.Process run = await io.Process.start(adbPath(), const [ + 'shell', + 'settings', + 'put', + 'secure', + 'enabled_accessibility_services', + 'com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService', + ]); + await run.exitCode; + + print('TalkBack version is ${await getTalkbackVersion()}'); +} + +Future disableTalkBack() async { + final io.Process run = await io.Process.start(adbPath(), const [ + 'shell', + 'settings', + 'put', + 'secure', + 'enabled_accessibility_services', + 'null', + ]); + await run.exitCode; +} diff --git a/dev/devicelab/lib/tasks/integration_tests.dart b/dev/devicelab/lib/tasks/integration_tests.dart index d663a2361a..0172d576fe 100644 --- a/dev/devicelab/lib/tasks/integration_tests.dart +++ b/dev/devicelab/lib/tasks/integration_tests.dart @@ -4,6 +4,7 @@ import '../framework/devices.dart'; import '../framework/framework.dart'; +import '../framework/talkback.dart'; import '../framework/task_result.dart'; import '../framework/utils.dart'; @@ -74,9 +75,10 @@ TaskFunction createHybridAndroidViewsIntegrationTest() { } TaskFunction createAndroidSemanticsIntegrationTest() { - return DriverTest( + return IntegrationTest( '${flutterDirectory.path}/dev/integration_tests/android_semantics_testing', - 'lib/main.dart', + 'integration_test/main_test.dart', + withTalkBack: true, ).call; } @@ -213,6 +215,7 @@ class IntegrationTest { this.testTarget, { this.extraOptions = const [], this.createPlatforms = const [], + this.withTalkBack = false, } ); @@ -220,6 +223,7 @@ class IntegrationTest { final String testTarget; final List extraOptions; final List createPlatforms; + final bool withTalkBack; Future call() { return inDirectory(testDirectory, () async { @@ -237,6 +241,13 @@ class IntegrationTest { ]); } + if (withTalkBack) { + if (device is! AndroidDevice) { + return TaskResult.failure('A test that enables TalkBack can only be run on Android devices'); + } + await enableTalkBack(); + } + final List options = [ '-v', '-d', @@ -246,6 +257,10 @@ class IntegrationTest { ]; await flutter('test', options: options); + if (withTalkBack) { + await disableTalkBack(); + } + return TaskResult.success(null); }); } 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 e8db455923..c623a84e3f 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 @@ -20,7 +20,6 @@ import android.content.ClipData; import android.content.Context; import androidx.annotation.NonNull; -import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.android.FlutterView; @@ -123,11 +122,60 @@ public class MainActivity extends FlutterActivity { if (actionList.size() > 0) { ArrayList actions = new ArrayList<>(); for (AccessibilityNodeInfo.AccessibilityAction action : actionList) { - actions.add(action.getId()); + if (kIdByAction.containsKey(action)) { + actions.add(kIdByAction.get(action).intValue()); + } } result.put("actions", actions); } return result; } } + + // These indices need to be in sync with android_semantics_testing/lib/src/constants.dart + static final int kFocusIndex = 1 << 0; + static final int kClearFocusIndex = 1 << 1; + static final int kSelectIndex = 1 << 2; + static final int kClearSelectionIndex = 1 << 3; + static final int kClickIndex = 1 << 4; + static final int kLongClickIndex = 1 << 5; + static final int kAccessibilityFocusIndex = 1 << 6; + static final int kClearAccessibilityFocusIndex = 1 << 7; + static final int kNextAtMovementGranularityIndex = 1 << 8; + static final int kPreviousAtMovementGranularityIndex = 1 << 9; + static final int kNextHtmlElementIndex = 1 << 10; + static final int kPreviousHtmlElementIndex = 1 << 11; + static final int kScrollForwardIndex = 1 << 12; + static final int kScrollBackwardIndex = 1 << 13; + static final int kCutIndex = 1 << 14; + static final int kCopyIndex = 1 << 15; + static final int kPasteIndex = 1 << 16; + static final int kSetSelectionIndex = 1 << 17; + static final int kExpandIndex = 1 << 18; + static final int kCollapseIndex = 1 << 19; + static final int kSetText = 1 << 21; + + static final Map kIdByAction = new HashMap() {{ + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_FOCUS, kFocusIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_FOCUS, kClearFocusIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_SELECT, kSelectIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_SELECTION, kClearSelectionIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK, kClickIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK, kLongClickIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS, kAccessibilityFocusIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS, kClearAccessibilityFocusIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, kNextAtMovementGranularityIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, kPreviousAtMovementGranularityIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_NEXT_HTML_ELEMENT, kNextHtmlElementIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_PREVIOUS_HTML_ELEMENT, kPreviousHtmlElementIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD, kScrollForwardIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD, kScrollBackwardIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_CUT, kCutIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_COPY, kCopyIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_PASTE, kPasteIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_SELECTION, kSetSelectionIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND, kExpandIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE, kCollapseIndex); + put(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT, kSetText); + }}; } diff --git a/dev/integration_tests/android_semantics_testing/android/project-app.lockfile b/dev/integration_tests/android_semantics_testing/android/project-app.lockfile index b5b0b745c3..df07b3a391 100644 --- a/dev/integration_tests/android_semantics_testing/android/project-app.lockfile +++ b/dev/integration_tests/android_semantics_testing/android/project-app.lockfile @@ -18,11 +18,16 @@ androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debu androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-core:3.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-idling-resource:3.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:rules:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window-java:1.0.0-beta03=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window:1.0.0-beta03=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.android.tools.analytics-library:protos:27.1.3=lintClassPath com.android.tools.analytics-library:shared:27.1.3=lintClassPath com.android.tools.analytics-library:tracker:27.1.3=lintClassPath @@ -54,16 +59,19 @@ com.android.tools:sdk-common:27.1.3=lintClassPath com.android.tools:sdklib:27.1.3=lintClassPath com.android:signflinger:4.1.3=lintClassPath com.android:zipflinger:4.1.3=lintClassPath -com.google.code.findbugs:jsr305:3.0.2=lintClassPath +com.google.code.findbugs:jsr305:2.0.1=debugAndroidTestCompileClasspath +com.google.code.findbugs:jsr305:3.0.2=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.google.code.gson:gson:2.8.5=lintClassPath -com.google.errorprone:error_prone_annotations:2.3.2=lintClassPath -com.google.guava:failureaccess:1.0.1=lintClassPath +com.google.errorprone:error_prone_annotations:2.3.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:failureaccess:1.0.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-android=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath com.google.guava:guava:28.1-jre=lintClassPath -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=lintClassPath -com.google.j2objc:j2objc-annotations:1.3=lintClassPath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath com.google.jimfs:jimfs:1.1=lintClassPath com.google.protobuf:protobuf-java:3.10.0=lintClassPath com.googlecode.json-simple:json-simple:1.1=lintClassPath +com.squareup:javawriter:2.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.squareup:javawriter:2.5.0=lintClassPath com.sun.activation:javax.activation:1.2.0=lintClassPath com.sun.istack:istack-commons-runtime:3.0.7=lintClassPath @@ -72,21 +80,26 @@ commons-codec:commons-codec:1.10=lintClassPath commons-logging:commons-logging:1.2=lintClassPath it.unimi.dsi:fastutil:7.2.0=lintClassPath javax.activation:javax.activation-api:1.2.0=lintClassPath -javax.inject:javax.inject:1=lintClassPath +javax.inject:javax.inject:1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath javax.xml.bind:jaxb-api:2.3.1=lintClassPath +junit:junit:4.12=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath net.sf.jopt-simple:jopt-simple:4.9=lintClassPath -net.sf.kxml:kxml2:2.3.0=lintClassPath +net.sf.kxml:kxml2:2.3.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.apache.commons:commons-compress:1.12=lintClassPath org.apache.httpcomponents:httpclient:4.5.6=lintClassPath org.apache.httpcomponents:httpcore:4.4.10=lintClassPath org.apache.httpcomponents:httpmime:4.5.6=lintClassPath org.bouncycastle:bcpkix-jdk15on:1.56=lintClassPath org.bouncycastle:bcprov-jdk15on:1.56=lintClassPath +org.checkerframework:checker-compat-qual:2.5.5=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath org.checkerframework:checker-qual:2.8.1=lintClassPath org.codehaus.groovy:groovy-all:2.4.15=lintClassPath -org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath +org.codehaus.mojo:animal-sniffer-annotations:1.18=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath +org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath diff --git a/dev/integration_tests/android_semantics_testing/android/project-integration_test.lockfile b/dev/integration_tests/android_semantics_testing/android/project-integration_test.lockfile new file mode 100644 index 0000000000..9d48dc0b09 --- /dev/null +++ b/dev/integration_tests/android_semantics_testing/android/project-integration_test.lockfile @@ -0,0 +1,122 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.6.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-core:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-idling-resource:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:rules:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.android.tools.analytics-library:protos:27.1.3=lintClassPath +com.android.tools.analytics-library:shared:27.1.3=lintClassPath +com.android.tools.analytics-library:tracker:27.1.3=lintClassPath +com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524=lintClassPath +com.android.tools.build:aapt2:4.1.3-6503028=_internal_aapt2_binary +com.android.tools.build:apksig:4.1.3=lintClassPath +com.android.tools.build:apkzlib:4.1.3=lintClassPath +com.android.tools.build:builder-model:4.1.3=lintClassPath +com.android.tools.build:builder-test-api:4.1.3=lintClassPath +com.android.tools.build:builder:4.1.3=lintClassPath +com.android.tools.build:gradle-api:4.1.3=lintClassPath +com.android.tools.build:manifest-merger:27.1.3=lintClassPath +com.android.tools.ddms:ddmlib:27.1.3=lintClassPath +com.android.tools.external.com-intellij:intellij-core:27.1.3=lintClassPath +com.android.tools.external.com-intellij:kotlin-compiler:27.1.3=lintClassPath +com.android.tools.external.org-jetbrains:uast:27.1.3=lintClassPath +com.android.tools.layoutlib:layoutlib-api:27.1.3=lintClassPath +com.android.tools.lint:lint-api:27.1.3=lintClassPath +com.android.tools.lint:lint-checks:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle-api:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle:27.1.3=lintClassPath +com.android.tools.lint:lint-model:27.1.3=lintClassPath +com.android.tools.lint:lint:27.1.3=lintClassPath +com.android.tools:annotations:27.1.3=lintClassPath +com.android.tools:common:27.1.3=lintClassPath +com.android.tools:dvlib:27.1.3=lintClassPath +com.android.tools:repository:27.1.3=lintClassPath +com.android.tools:sdk-common:27.1.3=lintClassPath +com.android.tools:sdklib:27.1.3=lintClassPath +com.android:signflinger:4.1.3=lintClassPath +com.android:zipflinger:4.1.3=lintClassPath +com.google.code.findbugs:jsr305:3.0.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.gson:gson:2.8.5=lintClassPath +com.google.errorprone:error_prone_annotations:2.3.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:failureaccess:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-android=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-jre=lintClassPath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.jimfs:jimfs:1.1=lintClassPath +com.google.protobuf:protobuf-java:3.10.0=lintClassPath +com.googlecode.json-simple:json-simple:1.1=lintClassPath +com.squareup:javawriter:2.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.squareup:javawriter:2.5.0=lintClassPath +com.sun.activation:javax.activation:1.2.0=lintClassPath +com.sun.istack:istack-commons-runtime:3.0.7=lintClassPath +com.sun.xml.fastinfoset:FastInfoset:1.2.15=lintClassPath +commons-codec:commons-codec:1.10=lintClassPath +commons-logging:commons-logging:1.2=lintClassPath +it.unimi.dsi:fastutil:7.2.0=lintClassPath +javax.activation:javax.activation-api:1.2.0=lintClassPath +javax.inject:javax.inject:1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.xml.bind:jaxb-api:2.3.1=lintClassPath +junit:junit:4.12=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.sf.jopt-simple:jopt-simple:4.9=lintClassPath +net.sf.kxml:kxml2:2.3.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.commons:commons-compress:1.12=lintClassPath +org.apache.httpcomponents:httpclient:4.5.6=lintClassPath +org.apache.httpcomponents:httpcore:4.4.10=lintClassPath +org.apache.httpcomponents:httpmime:4.5.6=lintClassPath +org.bouncycastle:bcpkix-jdk15on:1.56=lintClassPath +org.bouncycastle:bcprov-jdk15on:1.56=lintClassPath +org.checkerframework:checker-compat-qual:2.5.5=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.checkerframework:checker-qual:2.8.1=lintClassPath +org.codehaus.groovy:groovy-all:2.4.15=lintClassPath +org.codehaus.mojo:animal-sniffer-annotations:1.18=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath +org.glassfish.jaxb:txw2:2.3.1=lintClassPath +org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.trove4j:trove4j:20160824=lintClassPath +org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jvnet.staxex:stax-ex:1.8=lintClassPath +org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-util:7.0=lintClassPath +org.ow2.asm:asm:7.0=lintClassPath +empty=androidApis,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile diff --git a/dev/integration_tests/android_semantics_testing/integration_test/main_test.dart b/dev/integration_tests/android_semantics_testing/integration_test/main_test.dart new file mode 100644 index 0000000000..981a814e08 --- /dev/null +++ b/dev/integration_tests/android_semantics_testing/integration_test/main_test.dart @@ -0,0 +1,677 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; + +import 'package:android_semantics_testing/android_semantics_testing.dart'; +import 'package:android_semantics_testing/main.dart' as app; +import 'package:android_semantics_testing/test_constants.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +// 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, +]; + +const MethodChannel kSemanticsChannel = MethodChannel('semantics'); + +Future setClipboard(String message) async { + final Completer completer = Completer(); + Future completeSetClipboard([Object? _]) async { + await kSemanticsChannel.invokeMethod('setClipboard', { + 'message': message, + }); + completer.complete(); + } + if (SchedulerBinding.instance.hasScheduledFrame) { + SchedulerBinding.instance.addPostFrameCallback(completeSetClipboard); + } else { + completeSetClipboard(); + } + await completer.future; +} + +Future getSemantics(Finder finder, WidgetTester tester) async { + final int id = tester.getSemantics(finder).id; + final Completer completer = Completer(); + Future completeSemantics([Object? _]) async { + final dynamic result = await kSemanticsChannel.invokeMethod('getSemanticsNode', { + 'id': id, + }); + completer.complete(json.encode(result)); + } + if (SchedulerBinding.instance.hasScheduledFrame) { + SchedulerBinding.instance.addPostFrameCallback(completeSemantics); + } else { + completeSemantics(); + } + return AndroidSemanticsNode.deserialize(await completer.future); +} + +Future main() async { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('AccessibilityBridge', () { + group('TextField', () { + Future prepareTextField(WidgetTester tester) async { + app.main(); + await tester.pumpAndSettle(); + await tester.tap(find.text(textFieldRoute)); + await tester.pumpAndSettle(); + + // The text selection menu and related semantics vary depending on if + // the clipboard contents are pasteable. Copy some text into the + // clipboard to make sure these tests always run with pasteable content + // in the clipboard. + // Ideally this should test the case where there is nothing on the + // clipboard as well, but there is no reliable way to clear the + // clipboard on Android devices. + await setClipboard('Hello World'); + await tester.pumpAndSettle(); + } + + testWidgets('TextField has correct Android semantics', (WidgetTester tester) async { + final Finder normalTextField = find.descendant( + of: find.byKey(const ValueKey(normalTextFieldKeyValue)), + matching: find.byType(EditableText), + ); + + await prepareTextField(tester); + expect( + await getSemantics(normalTextField, tester), + hasAndroidSemantics( + className: AndroidClassName.editText, + isEditable: true, + isFocusable: true, + isFocused: false, + isPassword: false, + actions: [ + AndroidSemanticsAction.click, + ], + // We can't predict the a11y focus when the screen changes. + ignoredActions: ignoredAccessibilityFocusActions, + ), + ); + await tester.tap(normalTextField); + await tester.pumpAndSettle(); + + expect( + await getSemantics(normalTextField, tester), + hasAndroidSemantics( + className: AndroidClassName.editText, + isFocusable: true, + isFocused: true, + isEditable: true, + isPassword: false, + actions: [ + AndroidSemanticsAction.click, + AndroidSemanticsAction.paste, + AndroidSemanticsAction.setSelection, + AndroidSemanticsAction.setText, + ], + // We can't predict the a11y focus when the screen changes. + ignoredActions: ignoredAccessibilityFocusActions, + ), + ); + + await tester.enterText(normalTextField, 'hello world'); + await tester.pumpAndSettle(); + + expect( + await getSemantics(normalTextField, tester), + hasAndroidSemantics( + text: 'hello world', + className: AndroidClassName.editText, + isFocusable: true, + isFocused: true, + isEditable: true, + isPassword: false, + actions: [ + AndroidSemanticsAction.click, + AndroidSemanticsAction.paste, + AndroidSemanticsAction.setSelection, + AndroidSemanticsAction.setText, + AndroidSemanticsAction.previousAtMovementGranularity, + ], + // We can't predict the a11y focus when the screen changes. + ignoredActions: ignoredAccessibilityFocusActions, + ), + ); + }, timeout: Timeout.none); + + testWidgets('password TextField has correct Android semantics', (WidgetTester tester) async { + final Finder passwordTextField = find.descendant( + of: find.byKey(const ValueKey(passwordTextFieldKeyValue)), + matching: find.byType(EditableText), + ); + + await prepareTextField(tester); + expect( + await getSemantics(passwordTextField, tester), + hasAndroidSemantics( + className: AndroidClassName.editText, + isEditable: true, + isFocusable: true, + isFocused: false, + isPassword: true, + actions: [ + AndroidSemanticsAction.click, + ], + // We can't predict the a11y focus when the screen changes. + ignoredActions: ignoredAccessibilityFocusActions, + ), + ); + + await tester.tap(passwordTextField); + await tester.pumpAndSettle(); + + expect( + await getSemantics(passwordTextField, tester), + hasAndroidSemantics( + className: AndroidClassName.editText, + isFocusable: true, + isFocused: true, + isEditable: true, + isPassword: true, + actions: [ + AndroidSemanticsAction.click, + AndroidSemanticsAction.paste, + AndroidSemanticsAction.setSelection, + AndroidSemanticsAction.setText, + ], + // We can't predict the a11y focus when the screen changes. + ignoredActions: ignoredAccessibilityFocusActions, + ), + ); + + await tester.enterText(passwordTextField, 'hello world'); + await tester.pumpAndSettle(); + + expect( + await getSemantics(passwordTextField, tester), + hasAndroidSemantics( + text: '\u{2022}' * ('hello world'.length), + className: AndroidClassName.editText, + isFocusable: true, + isFocused: true, + isEditable: true, + isPassword: true, + actions: [ + AndroidSemanticsAction.click, + AndroidSemanticsAction.paste, + AndroidSemanticsAction.setSelection, + AndroidSemanticsAction.setText, + AndroidSemanticsAction.previousAtMovementGranularity, + ], + // We can't predict the a11y focus when the screen changes. + ignoredActions: ignoredAccessibilityFocusActions, + ), + ); + }, timeout: Timeout.none); + }); + + group('SelectionControls', () { + Future prepareSelectionControls(WidgetTester tester) async { + app.main(); + await tester.pumpAndSettle(); + await tester.tap(find.text(selectionControlsRoute)); + await tester.pumpAndSettle(); + } + + testWidgets('Checkbox has correct Android semantics', (WidgetTester tester) async { + final Finder checkbox = find.byKey(const ValueKey(checkboxKeyValue)); + final Finder disabledCheckbox = find.byKey(const ValueKey(disabledCheckboxKeyValue)); + + await prepareSelectionControls(tester); + expect( + await getSemantics(checkbox, tester), + hasAndroidSemantics( + className: AndroidClassName.checkBox, + isChecked: false, + isCheckable: true, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + ); + + await tester.tap(checkbox); + await tester.pumpAndSettle(); + + expect( + await getSemantics(checkbox, tester), + hasAndroidSemantics( + className: AndroidClassName.checkBox, + isChecked: true, + isCheckable: true, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + ); + expect( + await getSemantics(disabledCheckbox, tester), + hasAndroidSemantics( + className: AndroidClassName.checkBox, + isCheckable: true, + isEnabled: false, + ignoredActions: ignoredAccessibilityFocusActions, + actions: const [], + ), + ); + }, timeout: Timeout.none); + + testWidgets('Radio has correct Android semantics', (WidgetTester tester) async { + final Finder radio = find.byKey(const ValueKey(radio2KeyValue)); + + await prepareSelectionControls(tester); + expect( + await getSemantics(radio, tester), + hasAndroidSemantics( + className: AndroidClassName.radio, + isChecked: false, + isCheckable: true, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + ); + + await tester.tap(radio); + await tester.pumpAndSettle(); + + expect( + await getSemantics(radio, tester), + hasAndroidSemantics( + className: AndroidClassName.radio, + isChecked: true, + isCheckable: true, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + ); + }, timeout: Timeout.none); + + testWidgets('Switch has correct Android semantics', (WidgetTester tester) async { + final Finder switchFinder = find.byKey(const ValueKey(switchKeyValue)); + + await prepareSelectionControls(tester); + expect( + await getSemantics(switchFinder, tester), + hasAndroidSemantics( + className: AndroidClassName.toggleSwitch, + isChecked: false, + isCheckable: true, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + ); + + await tester.tap(switchFinder); + await tester.pumpAndSettle(); + + expect( + await getSemantics(switchFinder, tester), + hasAndroidSemantics( + className: AndroidClassName.toggleSwitch, + isChecked: true, + isCheckable: true, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + ); + }, timeout: Timeout.none); + + // Regression test for https://github.com/flutter/flutter/issues/20820. + testWidgets('Switch can be labeled', (WidgetTester tester) async { + final Finder switchFinder = find.byKey(const ValueKey(labeledSwitchKeyValue)); + + await prepareSelectionControls(tester); + expect( + await getSemantics(switchFinder, tester), + hasAndroidSemantics( + className: AndroidClassName.toggleSwitch, + isChecked: false, + isCheckable: true, + isEnabled: true, + isFocusable: true, + contentDescription: switchLabel, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + ); + }, timeout: Timeout.none); + }); + + group('Popup Controls', () { + Future preparePopupControls(WidgetTester tester) async { + app.main(); + await tester.pumpAndSettle(); + await tester.tap(find.text(popupControlsRoute)); + await tester.pumpAndSettle(); + } + + testWidgets('Popup Menu has correct Android semantics', (WidgetTester tester) async { + final Finder popupButton = find.byKey(const ValueKey(popupButtonKeyValue)); + + await preparePopupControls(tester); + expect( + await getSemantics(popupButton, tester), + hasAndroidSemantics( + className: AndroidClassName.button, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + ); + + await tester.tap(popupButton); + await tester.pumpAndSettle(); + + try { + for (final String item in popupItems) { + expect( + await getSemantics(find.byKey(ValueKey('$popupKeyValue.$item')), tester), + hasAndroidSemantics( + className: AndroidClassName.button, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + reason: "Popup $item doesn't have the right semantics", + ); + } + await tester.tap(find.byKey(ValueKey('$popupKeyValue.${popupItems.first}'))); + await tester.pumpAndSettle(); + + // Pop up the menu again, to verify that TalkBack gets the right answer + // more than just the first time. + await tester.tap(popupButton); + await tester.pumpAndSettle(); + + for (final String item in popupItems) { + expect( + await getSemantics(find.byKey(ValueKey('$popupKeyValue.$item')), tester), + hasAndroidSemantics( + className: AndroidClassName.button, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + reason: "Popup $item doesn't have the right semantics the second time", + ); + } + } finally { + await tester.tap(find.byKey(ValueKey('$popupKeyValue.${popupItems.first}'))); + } + }, timeout: Timeout.none); + + testWidgets('Dropdown Menu has correct Android semantics', (WidgetTester tester) async { + final Finder dropdownButton = find.byKey(const ValueKey(dropdownButtonKeyValue)); + + await preparePopupControls(tester); + expect( + await getSemantics(dropdownButton, tester), + hasAndroidSemantics( + className: AndroidClassName.button, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + ); + + await tester.tap(dropdownButton); + await tester.pumpAndSettle(); + + try { + for (final String item in popupItems) { + // There are two copies of each item, so we want to find the version + // that is in the overlay, not the one in the dropdown. + expect( + await getSemantics( + find.descendant( + of: find.byType(Scrollable), + matching: find.byKey(ValueKey('$dropdownKeyValue.$item')), + ), + tester, + ), + hasAndroidSemantics( + className: AndroidClassName.view, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + reason: "Dropdown $item doesn't have the right semantics", + ); + } + await tester.tap( + find.descendant( + of: find.byType(Scrollable), + matching: find.byKey(ValueKey('$dropdownKeyValue.${popupItems.first}')), + ), + ); + await tester.pumpAndSettle(); + + // Pop up the dropdown again, to verify that TalkBack gets the right answer + // more than just the first time. + await tester.tap(dropdownButton); + await tester.pumpAndSettle(); + + for (final String item in popupItems) { + // There are two copies of each item, so we want to find the version + // that is in the overlay, not the one in the dropdown. + expect( + await getSemantics( + find.descendant( + of: find.byType(Scrollable), + matching: find.byKey(ValueKey('$dropdownKeyValue.$item')), + ), + tester, + ), + hasAndroidSemantics( + className: AndroidClassName.view, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + reason: "Dropdown $item doesn't have the right semantics the second time.", + ); + } + } finally { + await tester.tap( + find.descendant( + of: find.byType(Scrollable), + matching: find.byKey(ValueKey('$dropdownKeyValue.${popupItems.first}')), + ), + ); + } + }, timeout: Timeout.none); + + testWidgets('Modal alert dialog has correct Android semantics', (WidgetTester tester) async { + final Finder alertButton = find.byKey(const ValueKey(alertButtonKeyValue)); + + await preparePopupControls(tester); + expect( + await getSemantics(alertButton, tester), + hasAndroidSemantics( + className: AndroidClassName.button, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + ); + + await tester.tap(alertButton); + await tester.pumpAndSettle(); + + try { + expect( + await getSemantics(find.byKey(const ValueKey('$alertKeyValue.OK')), tester), + hasAndroidSemantics( + className: AndroidClassName.button, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + reason: "Alert OK button doesn't have the right semantics", + ); + + for (final String item in ['Title', 'Body1', 'Body2']) { + expect( + await getSemantics(find.byKey(ValueKey('$alertKeyValue.$item')), tester), + hasAndroidSemantics( + className: AndroidClassName.view, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [], + ), + reason: "Alert $item button doesn't have the right semantics", + ); + } + + await tester.tap(find.byKey(const ValueKey('$alertKeyValue.OK'))); + await tester.pumpAndSettle(); + + // Pop up the alert again, to verify that TalkBack gets the right answer + // more than just the first time. + await tester.tap(alertButton); + await tester.pumpAndSettle(); + + expect( + await getSemantics(find.byKey(const ValueKey('$alertKeyValue.OK')), tester), + hasAndroidSemantics( + className: AndroidClassName.button, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [ + AndroidSemanticsAction.click, + ], + ), + reason: "Alert OK button doesn't have the right semantics", + ); + + for (final String item in ['Title', 'Body1', 'Body2']) { + expect( + await getSemantics(find.byKey(ValueKey('$alertKeyValue.$item')), tester), + hasAndroidSemantics( + className: AndroidClassName.view, + isChecked: false, + isCheckable: false, + isEnabled: true, + isFocusable: true, + ignoredActions: ignoredAccessibilityFocusActions, + actions: [], + ), + reason: "Alert $item button doesn't have the right semantics", + ); + } + } finally { + await tester.tap(find.byKey(const ValueKey('$alertKeyValue.OK'))); + } + }, timeout: Timeout.none); + }); + + group('Headings', () { + Future prepareHeading(WidgetTester tester) async { + app.main(); + await tester.pumpAndSettle(); + await tester.tap(find.text(headingsRoute)); + await tester.pumpAndSettle(); + } + + testWidgets('AppBar title has correct Android heading semantics', (WidgetTester tester) async { + await prepareHeading(tester); + expect( + await getSemantics(find.byKey(const ValueKey(appBarTitleKeyValue)), tester), + hasAndroidSemantics(isHeading: true), + ); + }, timeout: Timeout.none); + + testWidgets('body text does not have Android heading semantics', (WidgetTester tester) async { + await prepareHeading(tester); + expect( + await getSemantics(find.byKey(const ValueKey(bodyTextKeyValue)), tester), + hasAndroidSemantics(isHeading: false), + ); + }, timeout: Timeout.none); + }); + }); +} diff --git a/dev/integration_tests/android_semantics_testing/lib/main.dart b/dev/integration_tests/android_semantics_testing/lib/main.dart index 12afeada56..0305bac439 100644 --- a/dev/integration_tests/android_semantics_testing/lib/main.dart +++ b/dev/integration_tests/android_semantics_testing/lib/main.dart @@ -2,13 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_driver/driver_extension.dart'; import 'src/tests/controls_page.dart'; import 'src/tests/headings_page.dart'; @@ -16,49 +10,9 @@ import 'src/tests/popup_page.dart'; import 'src/tests/text_field_page.dart'; void main() { - timeDilation = 0.05; // remove animations. - enableFlutterDriverExtension(handler: dataHandler); runApp(const TestApp()); } -const MethodChannel kSemanticsChannel = MethodChannel('semantics'); - -Future dataHandler(String? message) async { - if (message != null && message.contains('getSemanticsNode')) { - final Completer completer = Completer(); - final int id = int.tryParse(message.split('#')[1]) ?? 0; - Future completeSemantics([Object? _]) async { - final dynamic result = await kSemanticsChannel.invokeMethod('getSemanticsNode', { - 'id': id, - }); - completer.complete(json.encode(result)); - } - if (SchedulerBinding.instance.hasScheduledFrame) { - SchedulerBinding.instance.addPostFrameCallback(completeSemantics); - } else { - completeSemantics(); - } - return completer.future; - } - if (message != null && message.contains('setClipboard')) { - final Completer completer = Completer(); - final String str = message.split('#')[1]; - Future completeSetClipboard([Object? _]) async { - await kSemanticsChannel.invokeMethod('setClipboard', { - 'message': str, - }); - completer.complete(''); - } - if (SchedulerBinding.instance.hasScheduledFrame) { - SchedulerBinding.instance.addPostFrameCallback(completeSetClipboard); - } else { - completeSetClipboard(); - } - return completer.future; - } - throw UnimplementedError(); -} - Map routes = { selectionControlsRoute : (BuildContext context) => const SelectionControlsPage(), popupControlsRoute : (BuildContext context) => const PopupControlsPage(), diff --git a/dev/integration_tests/android_semantics_testing/lib/src/constants.dart b/dev/integration_tests/android_semantics_testing/lib/src/constants.dart index 1ff8c14146..b3d30cf657 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/constants.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/constants.dart @@ -98,6 +98,7 @@ enum AndroidSemanticsAction { /// The Android id of the action. final int id; + // These indices need to be in sync with android_semantics_testing/android/app/src/main/java/com/yourcompany/platforminteraction/MainActivity.java static const int _kFocusIndex = 1 << 0; static const int _kClearFocusIndex = 1 << 1; static const int _kSelectIndex = 1 << 2; 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 15f43f7fd6..f650ea769f 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/matcher.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/matcher.dart @@ -77,7 +77,8 @@ class _AndroidSemanticsMatcher extends Matcher { this.isHeading, this.isPassword, this.isLongClickable, - }); + }) : assert(ignoredActions == null || actions != null, 'actions must not be null if ignoredActions is not null'), + assert(ignoredActions == null || !actions!.any(ignoredActions.contains)); final String? text; final String? className; @@ -115,6 +116,9 @@ class _AndroidSemanticsMatcher extends Matcher { if (actions != null) { description.add(' with actions: $actions'); } + if (ignoredActions != null) { + description.add(' with ignoredActions: $ignoredActions'); + } if (rect != null) { description.add(' with rect: $rect'); } @@ -170,13 +174,15 @@ class _AndroidSemanticsMatcher extends Matcher { } if (actions != null) { final List itemActions = item.getActions(); + if (ignoredActions != null) { + itemActions.removeWhere(ignoredActions!.contains); + } 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 = 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)) { + if (missingInString.isEmpty && unexpectedInString.isEmpty) { return true; } return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpectedInString\nMissing: $missingInString', matchState); diff --git a/dev/integration_tests/android_semantics_testing/pubspec.yaml b/dev/integration_tests/android_semantics_testing/pubspec.yaml index a18ab530d8..3957ea0cb3 100644 --- a/dev/integration_tests/android_semantics_testing/pubspec.yaml +++ b/dev/integration_tests/android_semantics_testing/pubspec.yaml @@ -6,7 +6,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_driver: + integration_test: sdk: flutter flutter_test: sdk: flutter 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 deleted file mode 100644 index 01e536ef14..0000000000 --- a/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart +++ /dev/null @@ -1,702 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io' as io; - -import 'package:android_semantics_testing/android_semantics_testing.dart'; -import 'package:android_semantics_testing/test_constants.dart'; -import 'package:flutter_driver/flutter_driver.dart'; -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) { - return 'adb'; - } else { - return path.join(androidHome, 'platform-tools', 'adb'); - } -} - -void main() { - group('AccessibilityBridge', () { - late FlutterDriver driver; - Future getSemantics(SerializableFinder finder) async { - final int id = await driver.getSemanticsId(finder); - final String data = await driver.requestData('getSemanticsNode#$id'); - return AndroidSemanticsNode.deserialize(data); - } - - // The version of TalkBack running on the device. - Version? talkbackVersion; - - Future getTalkbackVersion() async { - final io.ProcessResult result = await io.Process.run(adbPath(), const [ - 'shell', - 'dumpsys', - 'package', - 'com.google.android.marvin.talkback', - ]); - if (result.exitCode != 0) { - throw Exception('Failed to get TalkBack version: ${result.stdout as String}\n${result.stderr as String}'); - } - final List lines = (result.stdout as String).split('\n'); - String? version; - for (final String line in lines) { - if (line.contains('versionName')) { - version = line.replaceAll(RegExp(r'\s*versionName='), ''); - break; - } - } - if (version == null) { - throw Exception('Unable to determine TalkBack version.'); - } - - // Android doesn't quite use semver, so convert the version string to semver form. - final RegExp startVersion = RegExp(r'(?\d+)\.(?\d+)\.(?\d+)(\.(?\d+))?'); - final RegExpMatch? match = startVersion.firstMatch(version); - if (match == null) { - return Version(0, 0, 0); - } - return Version( - int.parse(match.namedGroup('major')!), - int.parse(match.namedGroup('minor')!), - int.parse(match.namedGroup('patch')!), - build: match.namedGroup('build'), - ); - } - - setUpAll(() async { - driver = await FlutterDriver.connect(); - talkbackVersion ??= await getTalkbackVersion(); - print('TalkBack version is $talkbackVersion'); - - // Say the magic words.. - final io.Process run = await io.Process.start(adbPath(), const [ - 'shell', - 'settings', - 'put', - 'secure', - 'enabled_accessibility_services', - 'com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService', - ]); - await run.exitCode; - }); - - tearDownAll(() async { - // ... And turn it off again - final io.Process run = await io.Process.start(adbPath(), const [ - 'shell', - 'settings', - 'put', - 'secure', - 'enabled_accessibility_services', - 'null', - ]); - await run.exitCode; - driver.close(); - }); - - group('TextField', () { - setUpAll(() async { - await driver.tap(find.text(textFieldRoute)); - // Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28 - await Future.delayed(const Duration(milliseconds: 500)); - - // The text selection menu and related semantics vary depending on if - // the clipboard contents are pasteable. Copy some text into the - // clipboard to make sure these tests always run with pasteable content - // in the clipboard. - // Ideally this should test the case where there is nothing on the - // clipboard as well, but there is no reliable way to clear the - // clipboard on Android devices. - await driver.requestData('setClipboard#Hello World'); - await Future.delayed(const Duration(milliseconds: 500)); - }); - - test('TextField has correct Android semantics', () async { - final SerializableFinder normalTextField = find.descendant( - of: find.byValueKey(normalTextFieldKeyValue), - matching: find.byType('Semantics'), - firstMatchOnly: true, - ); - expect( - await getSemantics(normalTextField), - hasAndroidSemantics( - className: AndroidClassName.editText, - isEditable: true, - isFocusable: true, - isFocused: false, - isPassword: false, - actions: [ - AndroidSemanticsAction.click, - ], - // We can't predict the a11y focus when the screen changes. - ignoredActions: ignoredAccessibilityFocusActions, - ), - ); - - await driver.tap(normalTextField); - // Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28 - await Future.delayed(const Duration(milliseconds: 500)); - - expect( - await getSemantics(normalTextField), - hasAndroidSemantics( - className: AndroidClassName.editText, - isFocusable: true, - isFocused: true, - isEditable: true, - isPassword: false, - actions: [ - AndroidSemanticsAction.click, - AndroidSemanticsAction.copy, - AndroidSemanticsAction.setSelection, - AndroidSemanticsAction.setText, - ], - // We can't predict the a11y focus when the screen changes. - ignoredActions: ignoredAccessibilityFocusActions, - ), - ); - - await driver.enterText('hello world'); - // Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28 - await Future.delayed(const Duration(milliseconds: 500)); - - expect( - await getSemantics(normalTextField), - hasAndroidSemantics( - text: 'hello world', - className: AndroidClassName.editText, - isFocusable: true, - isFocused: true, - isEditable: true, - isPassword: false, - actions: [ - AndroidSemanticsAction.click, - AndroidSemanticsAction.copy, - AndroidSemanticsAction.setSelection, - AndroidSemanticsAction.setText, - AndroidSemanticsAction.previousAtMovementGranularity, - ], - // We can't predict the a11y focus when the screen changes. - ignoredActions: ignoredAccessibilityFocusActions, - ), - ); - }, timeout: Timeout.none); - - test('password TextField has correct Android semantics', () async { - final SerializableFinder passwordTextField = find.descendant( - of: find.byValueKey(passwordTextFieldKeyValue), - matching: find.byType('Semantics'), - firstMatchOnly: true, - ); - expect( - await getSemantics(passwordTextField), - hasAndroidSemantics( - className: AndroidClassName.editText, - isEditable: true, - isFocusable: true, - isFocused: false, - isPassword: true, - actions: [ - AndroidSemanticsAction.click, - ], - // We can't predict the a11y focus when the screen changes. - ignoredActions: ignoredAccessibilityFocusActions, - ), - ); - - await driver.tap(passwordTextField); - // Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28 - await Future.delayed(const Duration(milliseconds: 500)); - - expect( - await getSemantics(passwordTextField), - hasAndroidSemantics( - className: AndroidClassName.editText, - isFocusable: true, - isFocused: true, - isEditable: true, - isPassword: true, - actions: [ - AndroidSemanticsAction.click, - AndroidSemanticsAction.copy, - AndroidSemanticsAction.setSelection, - AndroidSemanticsAction.setText, - ], - // We can't predict the a11y focus when the screen changes. - ignoredActions: ignoredAccessibilityFocusActions, - ), - ); - - await driver.enterText('hello world'); - // Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28 - await Future.delayed(const Duration(milliseconds: 500)); - - expect( - await getSemantics(passwordTextField), - hasAndroidSemantics( - text: '\u{2022}' * ('hello world'.length), - className: AndroidClassName.editText, - isFocusable: true, - isFocused: true, - isEditable: true, - isPassword: true, - actions: [ - AndroidSemanticsAction.click, - AndroidSemanticsAction.copy, - AndroidSemanticsAction.setSelection, - AndroidSemanticsAction.setText, - AndroidSemanticsAction.previousAtMovementGranularity, - ], - // We can't predict the a11y focus when the screen changes. - ignoredActions: ignoredAccessibilityFocusActions, - ), - ); - }, timeout: Timeout.none); - - tearDownAll(() async { - await driver.tap(find.byValueKey('back')); - }); - }); - - group('SelectionControls', () { - setUpAll(() async { - await driver.tap(find.text(selectionControlsRoute)); - }); - - test('Checkbox has correct Android semantics', () async { - Future getCheckboxSemantics(String key) async { - return getSemantics(find.byValueKey(key)); - } - expect( - await getCheckboxSemantics(checkboxKeyValue), - hasAndroidSemantics( - className: AndroidClassName.checkBox, - isChecked: false, - isCheckable: true, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - ); - - await driver.tap(find.byValueKey(checkboxKeyValue)); - - expect( - await getCheckboxSemantics(checkboxKeyValue), - hasAndroidSemantics( - className: AndroidClassName.checkBox, - isChecked: true, - isCheckable: true, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - ); - expect( - await getCheckboxSemantics(disabledCheckboxKeyValue), - hasAndroidSemantics( - className: AndroidClassName.checkBox, - isCheckable: true, - isEnabled: false, - ignoredActions: ignoredAccessibilityFocusActions, - actions: const [], - ), - ); - }, timeout: Timeout.none); - test('Radio has correct Android semantics', () async { - Future getRadioSemantics(String key) async { - return getSemantics(find.byValueKey(key)); - } - expect( - await getRadioSemantics(radio2KeyValue), - hasAndroidSemantics( - className: AndroidClassName.radio, - isChecked: false, - isCheckable: true, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - ); - - await driver.tap(find.byValueKey(radio2KeyValue)); - - expect( - await getRadioSemantics(radio2KeyValue), - hasAndroidSemantics( - className: AndroidClassName.radio, - isChecked: true, - isCheckable: true, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - ); - }, timeout: Timeout.none); - test('Switch has correct Android semantics', () async { - Future getSwitchSemantics(String key) async { - return getSemantics(find.byValueKey(key)); - } - expect( - await getSwitchSemantics(switchKeyValue), - hasAndroidSemantics( - className: AndroidClassName.toggleSwitch, - isChecked: false, - isCheckable: true, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - ); - - await driver.tap(find.byValueKey(switchKeyValue)); - - expect( - await getSwitchSemantics(switchKeyValue), - hasAndroidSemantics( - className: AndroidClassName.toggleSwitch, - isChecked: true, - isCheckable: true, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - ); - }, timeout: Timeout.none); - - // Regression test for https://github.com/flutter/flutter/issues/20820. - test('Switch can be labeled', () async { - Future getSwitchSemantics(String key) async { - return getSemantics(find.byValueKey(key)); - } - expect( - await getSwitchSemantics(labeledSwitchKeyValue), - hasAndroidSemantics( - className: AndroidClassName.toggleSwitch, - isChecked: false, - isCheckable: true, - isEnabled: true, - isFocusable: true, - contentDescription: switchLabel, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - ); - }, timeout: Timeout.none); - - tearDownAll(() async { - await driver.tap(find.byValueKey('back')); - }); - }); - - group('Popup Controls', () { - setUpAll(() async { - await driver.tap(find.text(popupControlsRoute)); - }); - - test('Popup Menu has correct Android semantics', () async { - expect( - await getSemantics(find.byValueKey(popupButtonKeyValue)), - hasAndroidSemantics( - className: AndroidClassName.button, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - ); - - await driver.tap(find.byValueKey(popupButtonKeyValue)); - try { - // We have to wait wall time here because we're waiting for TalkBack to - // catch up. - await Future.delayed(const Duration(milliseconds: 1500)); - - for (final String item in popupItems) { - expect( - await getSemantics(find.byValueKey('$popupKeyValue.$item')), - hasAndroidSemantics( - className: AndroidClassName.button, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - reason: "Popup $item doesn't have the right semantics"); - } - await driver.tap(find.byValueKey('$popupKeyValue.${popupItems.first}')); - - // Pop up the menu again, to verify that TalkBack gets the right answer - // more than just the first time. - await driver.tap(find.byValueKey(popupButtonKeyValue)); - await Future.delayed(const Duration(milliseconds: 1500)); - - for (final String item in popupItems) { - expect( - await getSemantics(find.byValueKey('$popupKeyValue.$item')), - hasAndroidSemantics( - className: AndroidClassName.button, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - reason: "Popup $item doesn't have the right semantics the second time"); - } - } finally { - await driver.tap(find.byValueKey('$popupKeyValue.${popupItems.first}')); - } - }, timeout: Timeout.none); - - test('Dropdown Menu has correct Android semantics', () async { - expect( - await getSemantics(find.byValueKey(dropdownButtonKeyValue)), - hasAndroidSemantics( - className: AndroidClassName.button, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - ); - - await driver.tap(find.byValueKey(dropdownButtonKeyValue)); - try { - await Future.delayed(const Duration(milliseconds: 1500)); - - for (final String item in popupItems) { - // There are two copies of each item, so we want to find the version - // that is in the overlay, not the one in the dropdown. - expect( - await getSemantics(find.descendant( - of: find.byType('Scrollable'), - matching: find.byValueKey('$dropdownKeyValue.$item'), - )), - hasAndroidSemantics( - className: AndroidClassName.view, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - reason: "Dropdown $item doesn't have the right semantics"); - } - await driver.tap( - find.descendant( - of: find.byType('Scrollable'), - matching: find.byValueKey('$dropdownKeyValue.${popupItems.first}'), - ), - ); - - // Pop up the dropdown again, to verify that TalkBack gets the right answer - // more than just the first time. - await driver.tap(find.byValueKey(dropdownButtonKeyValue)); - await Future.delayed(const Duration(milliseconds: 1500)); - - for (final String item in popupItems) { - // There are two copies of each item, so we want to find the version - // that is in the overlay, not the one in the dropdown. - expect( - await getSemantics(find.descendant( - of: find.byType('Scrollable'), - matching: find.byValueKey('$dropdownKeyValue.$item'), - )), - hasAndroidSemantics( - className: AndroidClassName.view, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - reason: "Dropdown $item doesn't have the right semantics the second time."); - } - } finally { - await driver.tap( - find.descendant( - of: find.byType('Scrollable'), - matching: find.byValueKey('$dropdownKeyValue.${popupItems.first}'), - ), - ); - } - }, timeout: Timeout.none); - - test('Modal alert dialog has correct Android semantics', () async { - expect( - await getSemantics(find.byValueKey(alertButtonKeyValue)), - hasAndroidSemantics( - className: AndroidClassName.button, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - ); - - await driver.tap(find.byValueKey(alertButtonKeyValue)); - try { - await Future.delayed(const Duration(milliseconds: 1500)); - - expect( - await getSemantics(find.byValueKey('$alertKeyValue.OK')), - hasAndroidSemantics( - className: AndroidClassName.button, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - reason: "Alert OK button doesn't have the right semantics"); - - for (final String item in ['Title', 'Body1', 'Body2']) { - expect( - await getSemantics(find.byValueKey('$alertKeyValue.$item')), - hasAndroidSemantics( - className: AndroidClassName.view, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [], - ), - reason: "Alert $item button doesn't have the right semantics"); - } - - await driver.tap(find.byValueKey('$alertKeyValue.OK')); - - // Pop up the alert again, to verify that TalkBack gets the right answer - // more than just the first time. - await driver.tap(find.byValueKey(alertButtonKeyValue)); - await Future.delayed(const Duration(milliseconds: 1500)); - - expect( - await getSemantics(find.byValueKey('$alertKeyValue.OK')), - hasAndroidSemantics( - className: AndroidClassName.button, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [ - AndroidSemanticsAction.click, - ], - ), - reason: "Alert OK button doesn't have the right semantics"); - - for (final String item in ['Title', 'Body1', 'Body2']) { - expect( - await getSemantics(find.byValueKey('$alertKeyValue.$item')), - hasAndroidSemantics( - className: AndroidClassName.view, - isChecked: false, - isCheckable: false, - isEnabled: true, - isFocusable: true, - ignoredActions: ignoredAccessibilityFocusActions, - actions: [], - ), - reason: "Alert $item button doesn't have the right semantics"); - } - } finally { - await driver.tap(find.byValueKey('$alertKeyValue.OK')); - } - }, timeout: Timeout.none); - - tearDownAll(() async { - await Future.delayed(const Duration(milliseconds: 500)); - await driver.tap(find.byValueKey('back')); - }); - }); - - group('Headings', () { - setUpAll(() async { - await driver.tap(find.text(headingsRoute)); - }); - - test('AppBar title has correct Android heading semantics', () async { - expect( - await getSemantics(find.byValueKey(appBarTitleKeyValue)), - hasAndroidSemantics(isHeading: true), - ); - }, timeout: Timeout.none); - - test('body text does not have Android heading semantics', () async { - expect( - await getSemantics(find.byValueKey(bodyTextKeyValue)), - hasAndroidSemantics(isHeading: false), - ); - }, timeout: Timeout.none); - - tearDownAll(() async { - await driver.tap(find.byValueKey('back')); - }); - }); - - }); -}