简体   繁体   English

Flutter 集成测试:通过 label 在 TextFormField 中输入文本

[英]Flutter integration testing : Enter text into TextFormField by label

How do I enter text into TextFormField by using the label text?如何使用 label 文本在TextFormField中输入文本? 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),
);
  • Pump your testwidget which contains your target TextFormField抽出包含目标 TextFormField 的 testwidget
  • Then find all the TextFormFields in your widget, add it to the List.然后在您的小部件中找到所有 TextFormFields,将其添加到列表中。
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您现在拥有所有文本字段,包括用户名标签字段

You were on the right track, just a couple of things needed adjusting.你走在正确的轨道上,只是有几件事需要调整。

TLDR; 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 “用户名”和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.首先,查找器找不到与“用户名”相关的EditableText ,因为TextFormField使用的EditableText实际上是与装饰相关的任何Text类型小部件的兄弟/表亲

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.您可以在大多数 IDE 中使用 Widget Inspector 查看树,或者如果您想完全留在测试环境中,请尝试debugDumpApp将树 output 放到控制台。 As you can see by this part of the tree, there's nothing under the EditableText that has the "Username" text in it.从树的这一部分可以看出, EditableText下没有任何内容包含“用户名”文本。

TextFormField tree TextFormField

│     └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 ancestordescendant

Secondly, you needed to use ancestor instead of descendant .其次,您需要使用ancestor而不是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从您of取景器开始
  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)?根据您of方法,您希望matching的查找器在树上找到备份(缩进较少),还是您要在树下更远(更多缩进)?
    • If you're going up, use ancestor如果你要上去,请使用ancestor
    • If you're going down, use descendant如果您要倒下,请使用descendant
  3. The ancestor / descendant method will return what matcher defines, not what of defines ancestor / descendant方法将返回matcher定义的内容,而不是定义of内容

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.我在这里猜测,但我认为您试图找到一个EditableText因为文档提到需要一个。 Thankfully, it also mentions that you can use something which has a descendant EditableText .值得庆幸的是,它还提到您可以使用具有后代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.因此,我们可以在matching参数中找到一个TextFormField而不是EditableText ,并且仍然使用该查找器和enterText方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM