fix(patrol_finders): enter text into edit text with hint finder#3136
fix(patrol_finders): enter text into edit text with hint finder#3136pdenert wants to merge 3 commits into
Conversation
When a string passed to enterText matches a non-hit-testable widget (e.g. the RichText rendering a TextField's hintText inside InputDecorator), waitUntilVisible would time out because the hint RichText is never hit-testable. Fix: before calling waitUntilVisible, check whether the finder already has matches that are all non-hit-testable. If so, walk up the element tree from the matched widget to find the first ancestor that contains an EditableText descendant, and use that EditableText as the effective target. EditableText is hit-testable, so waitUntilVisible succeeds and text entry proceeds normally.
There was a problem hiding this comment.
Code Review
This pull request fixes an issue where enterText() fails when matching non-hit-testable widgets (like hint-text inside an InputDecorator) by walking up the element tree to locate and target the sibling EditableText directly. Feedback points out a performance bottleneck in the element tree traversal, where creating and evaluating finders inside visitAncestorElements leads to an inefficient O(A x T) complexity. It is recommended to perform a direct local element traversal using visitChildren instead to avoid full-tree scans.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| Finder effectiveFinder = finder; | ||
| final earlyMatches = finder.evaluate(); | ||
| if (earlyMatches.isNotEmpty && | ||
| finder.hitTestable(at: alignment).evaluate().isEmpty) { | ||
| Finder? editableFinder; | ||
| earlyMatches.first.visitAncestorElements((ancestor) { | ||
| final parentFinder = | ||
| find.byElementPredicate((e) => e == ancestor); | ||
| final editable = find.descendant( | ||
| of: parentFinder, | ||
| matching: find.byType(EditableText), | ||
| ); | ||
| if (editable.evaluate().isNotEmpty) { | ||
| editableFinder = editable; | ||
| return false; | ||
| } | ||
| return true; | ||
| }); | ||
| if (editableFinder != null) { | ||
| effectiveFinder = editableFinder!; | ||
| } | ||
| } |
There was a problem hiding this comment.
Performance Issue: $O(A \times T)$ Complexity in Element Tree Traversal\n\nThe current implementation creates a new Finder (parentFinder and editable) and calls evaluate() on it for every ancestor element visited during visitAncestorElements.\n\nBecause find.byElementPredicate has to scan the entire element tree to find the element matching ancestor, and find.descendant performs a descendant search on that subtree, this results in an $O(A \times T)$ complexity (where $A$ is the number of ancestors and $T$ is the total number of elements in the widget tree).\n\nIn large widget trees, this can cause severe performance degradation, lag, or test timeouts.\n\n### Solution\n\nInstead of creating and evaluating finders at every ancestor level, perform a direct local element traversal using visitChildren to find the EditableText descendant. This avoids any full-tree scans or finder evaluation overhead during the ancestor walk, reducing the complexity to $O(T)$ (only a single byElementPredicate lookup at the end).
Finder effectiveFinder = finder;
final earlyMatches = finder.evaluate();
if (earlyMatches.isNotEmpty &&
finder.hitTestable(at: alignment).evaluate().isEmpty) {
Element? findEditableText(Element element) {
if (element.widget is EditableText) {
return element;
}
Element? found;
element.visitChildren((child) {
if (found != null) return;
final result = findEditableText(child);
if (result != null) {
found = result;
}
});
return found;
}
Element? editableElement;
earlyMatches.first.visitAncestorElements((ancestor) {
final element = findEditableText(ancestor);
if (element != null) {
editableElement = element;
return false;
}
return true;
});
if (editableElement != null) {
effectiveFinder = find.byElementPredicate((e) => e == editableElement);
}
}
Fix
enterText()failing when targeting aTextFieldby its hint textProblem
When
enterTextis called with a string that matches aTextField'shintText, the finder resolves to theRichTextwidget that renders the hint insideInputDecorator. This widget is purely visual and is never hit-testable, sowaitUntilVisible(which applies.hitTestable()internally) always timed out — even though the field was fully visible on screen.Root cause
createFindermaps aStringtofind.text(s, findRichText: true), which matches anyTextorRichTextin the tree — including hint-text nodes. The hintRichTextand the actualEditableTextare siblings underInputDecorator(not in an ancestor/descendant relationship), so simply searching for anEditableTextancestor of the matched node found nothing.Solution
At the start of
enterText, before callingwaitUntilVisible, check whether the finder already has matches but none are hit-testable. If so, walk up the element tree from the matched widget until an ancestor is found that contains anEditableTextdescendant. ThatEditableTextis used as the effective target for the rest of the method — it is hit-testable, and the existing descendant-search logic that follows already handles it correctly.No new imports are required:
EditableTextis already available frompackage:flutter/widgets.dart.