Skip to content

fix(patrol_finders): enter text into edit text with hint finder#3136

Open
pdenert wants to merge 3 commits into
masterfrom
fix/enter-text-hint-text-finder
Open

fix(patrol_finders): enter text into edit text with hint finder#3136
pdenert wants to merge 3 commits into
masterfrom
fix/enter-text-hint-text-finder

Conversation

@pdenert

@pdenert pdenert commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Fix enterText() failing when targeting a TextField by its hint text

Problem

When enterText is called with a string that matches a TextField's hintText, the finder resolves to the RichText widget that renders the hint inside InputDecorator. This widget is purely visual and is never hit-testable, so waitUntilVisible (which applies .hitTestable() internally) always timed out — even though the field was fully visible on screen.

// Previously threw WaitUntilVisibleTimeoutException
await $('You have entered this text').enterText('Hello, Flutter!');

Root cause

createFinder maps a String to find.text(s, findRichText: true), which matches any Text or RichText in the tree — including hint-text nodes. The hint RichText and the actual EditableText are siblings under InputDecorator (not in an ancestor/descendant relationship), so simply searching for an EditableText ancestor of the matched node found nothing.

Solution

At the start of enterText, before calling waitUntilVisible, 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 an EditableText descendant. That EditableText is 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: EditableText is already available from package:flutter/widgets.dart.

pdenert added 3 commits July 3, 2026 17:48
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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +438 to +459
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!;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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);
            }
          }

@Kendru98 Kendru98 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants