Home > OS >  Flutter integration testing : Enter text into TextFormField by label
Flutter integration testing : Enter text into TextFormField by label

Time:08-03

How do I enter text into TextFormField by using the label text? My issue is that I can't find the widget while doing an integration test. I was able to find the widget by adding a key but I don't want to change the source code of the app for doing integration testing.

TextFormField(
                controller: usernameController,
                decoration: InputDecoration(border: UnderlineInputBorder(), labelText: "Username"),
              )

Tried something like this but it doesn't work:

final usernameField = find.descendant(
  of: find.text("Username"),
  matching: find.byType(EditableText),
);

CodePudding user response:

  • Pump your testwidget which contains your target TextFormField
  • Then find all the TextFormFields in your widget, add it to the List.
List<TextField> textFields = List<TextField>();

find.byType(TextField).evaluate().toList().forEach((element) {
    textFields.add(element.widget);
});

  • You now have all the textfields including Username labeled field

CodePudding user response:

You were on the right track, just a couple of things needed adjusting.

TLDR;

Try this instead:

final usernameField = find.ancestor(
  of: find.text('Username'),
  matching: find.byType(TextFormField),
);

tester.enterText(usernameField, "testing");

Explanation

Relationship between "Username" and EditableText

First off, the finder couldn't find an EditableText related to "Username" because the EditableText used by TextFormField is actually a sibling/cousin to any Text type widgets related to the decoration.

You can take a look at the tree using the Widget Inspector in most IDEs, or if you want to stay completely in the test environment, try debugDumpApp to have the tree output to the console. As you can see by this part of the tree, there's nothing under the EditableText that has the "Username" text in it.

TextFormField tree

│     └TextFormField(dependencies: [UnmanagedRestorationScope, _InheritedTheme, _LocalizationsScope-[GlobalKey#1c7cf]], state: _TextFormFieldState#979c2)
│      └UnmanagedRestorationScope
│       └TextField(controller: TextEditingController#7d830(TextEditingValue(text: ┤├, selection: TextSelection.invalid, composing: TextRange(start: -1, end: -1))), enabled: true, decoration: InputDecoration(labelText: "Username", floatingLabelBehavior: FloatingLabelBehavior.auto, floatingLabelAlignment: FloatingLabelAlignment.start, border: UnderlineInputBorder(), alignLabelWithHint: false), dependencies: [DefaultSelectionStyle, MediaQuery, UnmanagedRestorationScope, _InheritedTheme, _LocalizationsScope-[GlobalKey#1c7cf]], state: _TextFieldState#b279d)
│        └MouseRegion(listeners: [enter, exit], cursor: SystemMouseCursor(text), renderObject: RenderMouseRegion#0773e relayoutBoundary=up3)
│         └TextFieldTapRegion(groupId: EditableText, renderObject: RenderTapRegion#81348)
│          └IgnorePointer(ignoring: false, renderObject: RenderIgnorePointer#4fd71 relayoutBoundary=up5)
│           └AnimatedBuilder(animation: TextEditingController#7d830(TextEditingValue(text: ┤├, selection: TextSelection.invalid, composing: TextRange(start: -1, end: -1))), state: _AnimatedState#59a41)
│            └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#eb417 relayoutBoundary=up6)
│             └TextSelectionGestureDetector(state: _TextSelectionGestureDetectorState#c5ea6)
│              └RawGestureDetector(state: RawGestureDetectorState#1eae5(gestures: [tap, long press, pan], excludeFromSemantics: true, behavior: translucent))
│               └Listener(listeners: [down, panZoomStart], behavior: translucent, renderObject: RenderPointerListener#84fb6 relayoutBoundary=up7)
│                └AnimatedBuilder(animation: Listenable.merge([FocusNode#3118d(context: Focus), TextEditingController#7d830(TextEditingValue(text: ┤├, selection: TextSelection.invalid, composing: TextRange(start: -1, end: -1)))]), state: _AnimatedState#6fcb9)
│                 └InputDecorator(decoration: InputDecoration(labelText: "Username", hintMaxLines: "1", floatingLabelBehavior: FloatingLabelBehavior.auto, floatingLabelAlignment: FloatingLabelAlignment.start, border: UnderlineInputBorder(), alignLabelWithHint: false), isFocused: false, isEmpty: true, dependencies: [Directionality, MediaQuery, _InheritedTheme, _LocalizationsScope-[GlobalKey#1c7cf]], state: _InputDecoratorState#cdcb7(tickers: tracking 2 tickers))
│                  └_Decorator(renderObject: _RenderDecoration#543bf relayoutBoundary=up8)
│                   ├RepaintBoundary(renderObject: RenderRepaintBoundary#932e5 relayoutBoundary=up9)
│                   │└UnmanagedRestorationScope
│                   │ └EditableText-[LabeledGlobalKey<EditableTextState>#62cb3](controller: TextEditingController#7d830(TextEditingValue(text: ┤├, selection: TextSelection.invalid, composing: TextRange(start: -1, end: -1))), focusNode: FocusNode#3118d, debugLabel: (englishLike titleMedium 2014).merge(blackMountainView titleMedium), inherit: false, color: Color(0xdd000000), family: Roboto, size: 16.0, weight: 400, baseline: alphabetic, decoration: TextDecoration.none, textAlign: start, keyboardType: TextInputType(name: TextInputType.text, signed: null, decimal: null), autofillHints: [], dependencies: [Directionality, MediaQuery, ScrollConfiguration, _EffectiveTickerMode], state: EditableTextState#1c1c0(tickers: tracking 1 ticker))
│                   │  └TextFieldTapRegion(debugLabel: EditableText, groupId: EditableText, renderObject: RenderTapRegion#9cbef)
│                   │   └MouseRegion(listeners: <none>, cursor: defer, renderObject: RenderMouseRegion#74908 relayoutBoundary=up11)
│                   │    └Actions(dispatcher: null, actions: {DoNothingAndStopPropagationTextIntent: DoNothingAction#4a34c, ReplaceTextIntent: CallbackAction<ReplaceTextIntent>#408b9, UpdateSelectionIntent: CallbackAction<UpdateSelectionIntent>#06984, DirectionalFocusIntent: DirectionalFocusAction#2491c, DismissIntent: CallbackAction<DismissIntent>#3e59f, DeleteCharacterIntent: _OverridableContextAction<DeleteCharacterIntent>#690f6(defaultAction: _DeleteTextAction<DeleteCharacterIntent>#0bc91), DeleteToNextWordBoundaryIntent: _OverridableContextAction<DeleteToNextWordBoundaryIntent>#c44ac(defaultAction: _DeleteTextAction<DeleteToNextWordBoundaryIntent>#119b9), DeleteToLineBreakIntent: _OverridableContextAction<DeleteToLineBreakIntent>#af3a8(defaultAction: _DeleteTextAction<DeleteToLineBreakIntent>#9eaed), ExtendSelectionByCharacterIntent: _OverridableContextAction<ExtendSelectionByCharacterIntent>#78905(defaultAction: _UpdateTextSelectionAction<ExtendSelectionByCharacterIntent>#3cb9a), ExtendSelectionToNextWordBoundaryIntent: _OverridableContextAction<ExtendSelectionToNextWordBoundaryIntent>#efea3(defaultAction: _UpdateTextSelectionAction<ExtendSelectionToNextWordBoundaryIntent>#11fdb), ExtendSelectionToLineBreakIntent: _OverridableContextAction<ExtendSelectionToLineBreakIntent>#9cdf0(defaultAction: _UpdateTextSelectionAction<ExtendSelectionToLineBreakIntent>#64825), ExpandSelectionToLineBreakIntent: _OverridableAction<ExpandSelectionToLineBreakIntent>#44e24(defaultAction: CallbackAction<ExpandSelectionToLineBreakIntent>#ce179), ExpandSelectionToDocumentBoundaryIntent: _OverridableAction<ExpandSelectionToDocumentBoundaryIntent>#1f49f(defaultAction: CallbackAction<ExpandSelectionToDocumentBoundaryIntent>#faeb6), ExtendSelectionVerticallyToAdjacentLineIntent: _OverridableContextAction<ExtendSelectionVerticallyToAdjacentLineIntent>#2444b(defaultAction: _UpdateTextSelectionToAdjacentLineAction<ExtendSelectionVerticallyToAdjacentLineIntent>#efbca), ExtendSelectionToDocumentBoundaryIntent: _OverridableContextAction<ExtendSelectionToDocumentBoundaryIntent>#a9b51(defaultAction: _UpdateTextSelectionAction<ExtendSelectionToDocumentBoundaryIntent>#d1374), ExtendSelectionToNextWordBoundaryOrCaretLocationIntent: _OverridableContextAction<ExtendSelectionToNextWordBoundaryOrCaretLocationIntent>#913d5(defaultAction: _ExtendSelectionOrCaretPositionAction#00b6c), ScrollToDocumentBoundaryIntent: _OverridableAction<ScrollToDocumentBoundaryIntent>#96e26(defaultAction: CallbackAction<ScrollToDocumentBoundaryIntent>#f52dc), SelectAllTextIntent: _OverridableContextAction<SelectAllTextIntent>#208ba(defaultAction: _SelectAllAction#1c128), CopySelectionTextIntent: _OverridableContextAction<CopySelectionTextIntent>#31482(defaultAction: _CopySelectionAction#d777b), PasteTextIntent: _OverridableAction<PasteTextIntent>#17ba0(defaultAction: CallbackAction<PasteTextIntent>#fc2e6), TransposeCharactersIntent: _OverridableAction<TransposeCharactersIntent>#909dc(defaultAction: CallbackAction<TransposeCharactersIntent>#826be)}, state: _ActionsState#6345b)
│                   │     └_ActionsMarker
│                   │      └_TextEditingHistory(state: _TextEditingHistoryState#c5c7e)
│                   │       └Actions(dispatcher: null, actions: {UndoTextIntent: _OverridableAction<UndoTextIntent>#76c57(defaultAction: CallbackAction<UndoTextIntent>#a6b68), RedoTextIntent: _OverridableAction<RedoTextIntent>#4244e(defaultAction: CallbackAction<RedoTextIntent>#d89b9)}, state: _ActionsState#a1a96)
│                   │        └_ActionsMarker
│                   │         └Focus(debugLabel: "EditableText", focusNode: FocusNode#3118d, dependencies: [_FocusMarker], state: _FocusState#f3479)
│                   │          └_FocusMarker
│                   │           └Scrollable(axisDirection: right, physics: null, restorationId: "editable", dependencies: [MediaQuery, UnmanagedRestorationScope, _InheritedTheme, _LocalizationsScope-[GlobalKey#1c7cf]], state: ScrollableState#2fb51(position: ScrollPositionWithSingleContext#ec8ce(offset: 0.0, range: 0.0..0.0, viewport: 800.0, ScrollableState, ClampingScrollPhysics -> RangeMaintainingScrollPhysics -> ClampingScrollPhysics -> RangeMaintainingScrollPhysics, IdleScrollActivity#259e6, ScrollDirection.idle), effective physics: ClampingScrollPhysics -> RangeMaintainingScrollPhysics -> ClampingScrollPhysics -> RangeMaintainingScrollPhysics))
│                   │            └_ScrollableScope
│                   │             └Listener(listeners: [signal], behavior: deferToChild, renderObject: RenderPointerListener#dd2ea relayoutBoundary=up12)
│                   │              └RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#d6db0](state: RawGestureDetectorState#cda6b(gestures: <none>, excludeFromSemantics: true, behavior: opaque))
│                   │               └Listener(listeners: [down, panZoomStart], behavior: opaque, renderObject: RenderPointerListener#0abf9 relayoutBoundary=up13)
│                   │                └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#6f0e4 relayoutBoundary=up14)
│                   │                 └IgnorePointer-[GlobalKey#f9ddb](ignoring: false, ignoringSemantics: false, renderObject: RenderIgnorePointer#015fc relayoutBoundary=up15)
│                   │                  └CompositedTransformTarget(renderObject: RenderLeaderLayer#bd9fd relayoutBoundary=up16)
│                   │                   └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#20253 relayoutBoundary=up17)
│                   │                    └_ScribbleFocusable(state: _ScribbleFocusableState#6511f)
│                   │                     └_Editable-[GlobalKey#9b870](dependencies: [_LocalizationsScope-[GlobalKey#1c7cf]], renderObject: RenderEditable#7f9ac relayoutBoundary=up18)
│                   ├_Shaker(animation: AnimationController#c202f(⏮ 0.000; paused), state: _AnimatedState#77802)
│                   │└Transform(dependencies: [Directionality], renderObject: RenderTransform#053a1 relayoutBoundary=up9)
│                   │ └AnimatedOpacity(duration: 200ms, opacity: 1.0, state: _AnimatedOpacityState#bcd55(ticker inactive))
│                   │  └FadeTransition(opacity: AnimationController#04a30(⏮ 0.000; paused; for AnimatedOpacity)➩Cubic(0.40, 0.00, 0.20, 1.00)➩Tween<double>(1.0 → 1.0)➩1.0, renderObject: RenderAnimatedOpacity#05cbb relayoutBoundary=up10)
│                   │   └AnimatedDefaultTextStyle(duration: 200ms, debugLabel: (((englishLike titleMedium 2014).merge(blackMountainView titleMedium)).merge(unknown)).copyWith, inherit: false, color: Color(0x99000000), family: Roboto, size: 16.0, weight: 400, baseline: alphabetic, height: 1.0x, decoration: TextDecoration.none, softWrap: wrapping at box width, overflow: clip, state: _AnimatedDefaultTextStyleState#6c250(ticker inactive))
│                   │    └DefaultTextStyle(debugLabel: (((englishLike titleMedium 2014).merge(blackMountainView titleMedium)).merge(unknown)).copyWith, inherit: false, color: Color(0x99000000), family: Roboto, size: 16.0, weight: 400, baseline: alphabetic, height: 1.0x, decoration: TextDecoration.none, softWrap: wrapping at box width, overflow: clip)
│                   │     └Text("Username", textAlign: start, overflow: ellipsis, dependencies: [DefaultSelectionStyle, DefaultTextStyle, MediaQuery])
│                   │      └RichText(softWrap: wrapping at box width, overflow: ellipsis, maxLines: unlimited, text: "Username", dependencies: [Directionality, _LocalizationsScope-[GlobalKey#1c7cf]], renderObject: RenderParagraph#128a5 relayoutBoundary=up11)
│                   ├_HelperError(state: _HelperErrorState#ffb07(ticker inactive))
│                   │└SizedBox(renderObject: RenderConstrainedBox#8887a relayoutBoundary=up9)
│                   └_BorderContainer(dependencies: [Directionality], state: _BorderContainerState#c1c2f(tickers: tracking 2 tickers))
│                    └CustomPaint(renderObject: RenderCustomPaint#f9064)

ancestor vs. descendant

Secondly, you needed to use ancestor instead of descendant. These two methods can be a bit difficult to wrap your head around, but this is how I think of it to keep them straight.

  1. Start with your of finder
  2. From your of method, is the thing you want your matching finder to find back up the tree (less indented), or are you going further down the tree (more indented)?
    • If you're going up, use ancestor
    • If you're going down, use descendant
  3. The ancestor/descendant method will return what matcher defines, not what of defines

Entering text

I'm taking a guess here, but I think that you were trying to find an EditableText because the documentation mentions needing one. Thankfully, it also mentions that you can use something which has a descendant EditableText.

As such, we can find a TextFormField instead of a EditableText in the matching parameter and still use that finder with the enterText method.

  • Related