-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Type of Request
Bug
Environment
Running the Linux_pixel_7pro integration_ui_keyboard_resize test on an Android device with the Flutter Driver
- Application under test: https://github.com/flutter/flutter/blob/master/dev/integration_tests/ui/lib/keyboard_resize.dart
- Test: https://github.com/flutter/flutter/blob/master/dev/integration_tests/ui/test_driver/keyboard_resize_test.dart
The goal of the test is: "Ensure keyboard dismissal resizes the view to original size" (eg not directly related to this bug)
What is happening?
Depending on the device executing the test and the device running the test, the following happens infrequently (but frequently enough to make the test flaky)
- The test launches the keyboard_resize test application using Flutter Driver in
enableTextEntryEmulation: falsemode - The test waits for the
defaultTextFieldTextField to be available viaawait driver.waitFor(defaultTextField); - The test taps on the
defaultTextFieldTextField viaawait driver.tap(defaultTextField); - The
defaultTextFieldTextField gets focus - The Android keyboard is not opening. The test fails as it needs the keyboard open to proceed. The logs contain a warning from InputManager about not opening the keyboard:
W/InputMethodManager(20089): Ignoring showSoftInput() as view=io.flutter.embedding.android.FlutterView{636da98 VFE...... .F....ID 0,0-1080,2277 #1} is not served.
Expected: InputMethodManager opens the keyboard.
Video of this happening (runs the tests multiple times, fails at the last one): https://drive.google.com/file/d/1dQmIPs7mCVjxocCvWB9KSfsaSiB7_Tyh/view?usp=sharing
Easily reproducible on MacBook Pro M1 + Pixel 8 Pro API 36. Also, with Pixel 9 Pro XL.
In LUCY, it is happening on Pixel 7 Pro (API unknown)
Does not happen or happens very rarely on older Android devices. Not reproducible with Pixel 3.
Reproduce
NOTE: If the workaround in #162308 is already merged, then the test will not fail anymore, but logs will show the retries. If you prefer to debug with failure, then move the await driver.tap(defaultTextField); or use this infinite loop setup with additional logs: https://github.com/harri35/flutter/blob/fe80784cddb7dfdad1538cff26161abd34ec5e01/dev/integration_tests/ui/test_driver/keyboard_resize_test.dart#L41
- Disable automatic reboots: Comment out the
await checkForRebootRequired();line influtter/dev/devicelab/lib/framework/framework.dart
Lines 240 to 243 in 9e273d5
} finally { await checkForRebootRequired(); await forceQuitRunningProcesses(); } - Disable automatic retries: Set
static const int retryNumber = 0;influtter/dev/devicelab/lib/framework/cocoon.dart
Lines 56 to 57 in 9e273d5
/// Threshold to auto retry a failed test. static const int retryNumber = 2; - Install
hyperfine: This is a command-line benchmarking tool (e.g.,brew install hyperfineon macOS). - Connect a device/emulator: Connect a newer physical Android device. Use a direct cable between the computer and device, skip any slow USB hubs.
- Navigate to devicelab: Navigate to the flutter\dev\devicelab\ directory
- Run repeated tests: Use
hyperfineto run the test multiple times and capture the output:
hyperfine -r 2000 --style=full 'dart bin/test_runner.dart test -t integration_ui_keyboard_resize >> log.txt'- Observe the logs:: Look for the
W/InputMethodManager(...): Ignoring showSoftInput() ..
Note: If you use the setup with additional logs, then this command is useful on Mac:
grep -wo -F -i -E "(No|[0-9]+) retries were used to make this PASS\." log.txt | sort | uniq -c
Additional details
When running the test 2000x times, the following patterns can be observed.
Iterations it takes to open the keyboard if infinite retries are allowed:
MacBook Pro M1 + Pixel 8 Pro API 36 - at 2000/2000 successful tests
569x "No retries were used to make this PASS."
1428x "1 retries were used to make this PASS."
1x "31 retries were used to make this PASS."
1x "53 retries were used to make this PASS."
1x "54 retries were used to make this PASS."
Logs have the following pattern - the keyboard can only be opened after the D/InsetsController(...): hide(ime(), fromIme=false) appears in the log.
...
<-Test starts->
.. end-to-end test Ensure keyboard dismissal resizes the view to original size ..
<- All attempts to open the keyboard fail in this period ->
D/InsetsController(...): hide(ime(), fromIme=false)
<- All attempts to open the keyboard succeed now ->
I/ImeTracker(...): com.yourcompany.integration_ui:...: onCancelled at PHASE_CLIENT_ALREADY_HIDDEN
I/ImeTracker(...): com.yourcompany.integration_ui:...: onRequestShow at ORIGIN_CLIENT reason SHOW_SOFT_INPUT fromUser false
D/InsetsController(12023): show(ime(), fromIme=false)
<- Keyboard opens somewhere here .. ->
D/InsetsController(12023): Setting requestedVisibleTypes to -1 (was -9)
D/InputConnectionAdaptor(12023): The input method toggled cursor monitoring on
...
Looking a the timing,
- In the tests with no retries or 1 retry the
D/InsetsController(...): hide(ime(), fromIme=false)takes ~ 1 second to appear from start of the test - In the test with 54 retries it takes 21 seconds to appear
The D/InsetsController(...): hide(ime(), fromIme=false) log seems to be coming from https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/InsetsController.java;l=1208
Note: Using waitForTappable makes no difference.
All background and investigation
For all details and info, see