#flutter #dart #flutterdriver
#flutter #dart #flutterdriver
Вопрос:
Я пишу интеграционный тест с использованием драйвера Flutter для приложения, использующего библиотеку CoachMark (https://pub.dev/packages/tutorial_coach_mark )
я хочу щелкнуть текст, чтобы закрыть метку, но когда я попытался проверить его с помощью инспектора виджетов VSCode, текст не отображался в дереве виджетов, когда я навожу инспектор на этот текст, он указывает на MaterialApp
корневой виджет (см. Скриншот)
Это методы, которые я безуспешно пытался найти:
find.byType('Text')
find.text('OKE')
find.byType('RichText')
- и даже этот вложенный, сбивающий с толку finder
return find.descendant(of: find.byType('Align'), matching: find.descendant(of:find.byType('SafeArea'), matching: find.descendant(of:find.byType('AnimatedOpacity'), matching: find.descendant(of:find.byType('InkWell'), matching: find.descendant(of: find.byType('Padding'),matching: find.text('OKE'))))));
Причина, по которой я пытался использовать 4-й метод, заключается в том, что когда я попытался погрузиться в сам код библиотеки, он создает виджет примерно так
Widget _buildSkip() {
if (widget.hideSkip) {
return SizedBox.shrink();
}
return Align(
alignment: widget.alignSkip,
child: SafeArea(
child: AnimatedOpacity(
opacity: showContent ? 1 : 0,
duration: Duration(milliseconds: 300),
child: InkWell(
onTap: widget.clickSkip,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
widget.textSkip,
style: widget.textStyleSkip
),
),
),
),
),
);
}
я приложил еще один скриншот, чтобы показать, что я хочу щелкнуть (текст в правом нижнем углу экрана)
есть предложения?
Редактировать
это начальный экран:
class HomeSearchBarWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BaseWidget<HomeSearchBarViewModel>(
model: HomeSearchBarViewModel(
Provider.of<TrackingService>(context),
Provider.of<ErrorReportingService>(context),
),
onModelReady: (model) => model.initModel(),
builder: (context, model, child) {
return Container(
padding: EdgeInsets.only(bottom: 5.0),
margin: EdgeInsets.only(top: 8, left: 16, right: 8),
alignment: Alignment.centerLeft,
width: MediaQuery.of(context).size.width,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
ImageHelper.logo,
UIHelper.horzSpace(16),
Expanded(
child: TextFormField(
readOnly: true,
decoration: new InputDecoration(
contentPadding: EdgeInsets.all(14),
labelStyle: PinTextStyles.styleBody2(
PinColorsV2.neutral500,
),
prefixIcon: Icon(
Icons.search,
color: PinColorsV2.neutral500,
),
border: UIHelper.inputBorder,
hintText: "Lokasi atau nama proyek",
hintStyle: PinTextStyles.styleBody2(
PinColorsV2.neutral200,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: PinColorsV2.neutral200,
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: PinColorsV2.neutral200,
),
),
),
onTap: () async {
await model.trackLogEvent(
HomeSearchBarTrackingKeys.clickSearch,
);
Navigator.pushNamed(
context,
RoutePaths.ProjectSearch,
arguments: {
"keyword": "",
},
);
},
),
),
UIHelper.horzSpace(12),
CoachMarkWidget(
targets: model.targets,
keyTarget: model.helpCenterKey,
targetIdentify: "Help-Center",
title: "Punya pertanyaan terkait penggunaan aplikasi?",
description:
"Temukan semua solusinya dengan tap ikon tanda tanya di sudut kanan atas.",
onFinish: () => model.hideCoachMark(),
isVisible: model.coachMark != null amp;amp; model.coachMark.value,
focusWidget: HelpCenterIconWidget(
page: HelpCenterPage.homePage,
iconColor: PinColorsV2.neutral500,
screenNameFrom: HomeViewTrackingKeys.open,
),
),
],
),
],
),
);
},
);
}
}
class HomeSearchBarTrackingKeys {
static const String clickSearch = "landing_click_search";
}
и это код для создания виджета:
class CoachMarkWidget extends StatelessWidget {
final List<TargetFocus> targets;
final GlobalKey keyTarget;
final String targetIdentify;
final String title;
final String description;
final bool isVisible;
final Function() onFinish;
final Widget focusWidget;
CoachMarkWidget({
this.targets,
this.keyTarget,
this.targetIdentify,
this.title,
this.description,
this.isVisible = true,
this.onFinish,
this.focusWidget,
});
void initTargetCoachMark() {
return targets.add(
TargetFocus(
identify: targetIdentify,
keyTarget: keyTarget,
contents: [
ContentTarget(
align: AlignContent.bottom,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 48,
),
child: Column(
children: <Widget>[
Text(
title,
style: PinTextStylesV2.styleHeadingXSmall(
color: PinColorsV2.neutralWhite,
).merge(
TextStyle(
height: 1.22,
),
),
),
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
description,
style: PinTextStylesV2.styleParagraphLarge(
color: PinColorsV2.neutralWhite,
).merge(
TextStyle(
height: 1.5,
),
),
),
),
],
),
),
),
],
),
);
}
showTutorial(
BuildContext context,
) {
return TutorialCoachMark(
context,
targets: targets,
colorShadow: PinColorsV2.blue300,
opacityShadow: 0.85,
textSkip: "OKE",
widgetKey: Key('OKE'),
// key: Key("oke"),
textStyleSkip: PinTextStylesV2.styleActionMedium(
color: PinColorsV2.neutralWhite,
),
onFinish: () async {
await onFinish();
},
onClickTarget: (target) async {
await onFinish();
},
onClickSkip: () async {
await onFinish();
},
)..show();
}
@override
Widget build(BuildContext context) {
initTargetCoachMark();
if (isVisible) {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
showTutorial(context);
},
);
}
return Container(
key: keyTarget,
child: focusWidget,
);
}
}
и вот пользовательский код виджета
class TutorialCoachMark{
final BuildContext _context;
final List<TargetFocus> targets;
final Function(TargetFocus) onClickTarget;
final Function() onFinish;
final double paddingFocus;
final Function() onClickSkip;
final AlignmentGeometry alignSkip;
final String textSkip;
final TextStyle textStyleSkip;
final bool hideSkip;
final Color colorShadow;
final double opacityShadow;
final GlobalKey<TutorialCoachMarkWidgetState> _widgetKey = GlobalKey();
final Key widgetKey;
OverlayEntry _overlayEntry;
TutorialCoachMark(
this._context, {
this.targets,
this.colorShadow = Colors.black,
this.onClickTarget,
this.onFinish,
this.paddingFocus = 10,
this.onClickSkip,
this.alignSkip = Alignment.bottomRight,
this.textSkip = "SKIP",
this.textStyleSkip = const TextStyle(color: Colors.white),
this.hideSkip = false,
this.opacityShadow = 0.8,
this.widgetKey
}) : assert(targets != null, opacityShadow >= 0 amp;amp; opacityShadow <= 1);
OverlayEntry _buildOverlay() {
return OverlayEntry(builder: (context) {
return TutorialCoachMarkWidget(
key: _widgetKey,
// key: widgetKey,
// text: widgetKey,
// dua diatas ini tambahan (key nya tadinya pake yang line atas)
targets: targets,
clickTarget: onClickTarget,
paddingFocus: paddingFocus,
clickSkip: skip,
alignSkip: alignSkip,
textSkip: textSkip,
textStyleSkip: textStyleSkip,
hideSkip: hideSkip,
colorShadow: colorShadow,
opacityShadow: opacityShadow,
finish: finish,
);
});
}
// @override
// Widget build(BuildContext context){
// show();
// }
void show() {
if (_overlayEntry == null) {
_overlayEntry = _buildOverlay();
Overlay.of(_context).insert(_overlayEntry);
}
}
void finish() {
if (onFinish != null) onFinish();
_removeOverlay();
}
void skip() {
if (onClickSkip != null) onClickSkip();
_removeOverlay();
}
void next() => _widgetKey?.currentState?.next();
void previous() => _widgetKey?.currentState?.previous();
void _removeOverlay() {
_overlayEntry?.remove();
_overlayEntry = null;
}
}
и вот кнопка «ОКЕ», которую я хочу нажать
class TutorialCoachMarkWidget extends StatefulWidget {
const TutorialCoachMarkWidget({
Key key,
// this.key = Key('OKE'),
this.targets,
this.finish,
this.paddingFocus = 10,
this.clickTarget,
this.alignSkip = Alignment.bottomRight,
this.textSkip = "SKIP",
this.clickSkip,
this.colorShadow = Colors.black,
this.opacityShadow = 0.8,
this.textStyleSkip = const TextStyle(color: Colors.white),
this.hideSkip,
}) : super(key: key);
final List<TargetFocus> targets;
final Function(TargetFocus) clickTarget;
final Function() finish;
final Color colorShadow;
final double opacityShadow;
final double paddingFocus;
final Function() clickSkip;
final AlignmentGeometry alignSkip;
final String textSkip;
final TextStyle textStyleSkip;
final bool hideSkip;
// final Key key; ini diganti sama line 44
@override
TutorialCoachMarkWidgetState createState() => TutorialCoachMarkWidgetState();
}
class TutorialCoachMarkWidgetState extends State<TutorialCoachMarkWidget> {
final GlobalKey<AnimatedFocusLightState> _focusLightKey = GlobalKey();
final Key textKey = Key('OKE');
bool showContent = false;
TargetFocus currentTarget;
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Stack(
children: <Widget>[
AnimatedFocusLight(
key: _focusLightKey,
targets: widget.targets,
finish: widget.finish,
paddingFocus: widget.paddingFocus,
colorShadow: widget.colorShadow,
opacityShadow: widget.opacityShadow,
clickTarget: (target) {
if (widget.clickTarget != null) widget.clickTarget(target);
},
focus: (target) {
setState(() {
currentTarget = target;
showContent = true;
});
},
removeFocus: () {
setState(() {
showContent = false;
});
},
),
AnimatedOpacity(
opacity: showContent ? 1 : 0,
duration: Duration(milliseconds: 300),
child: _buildContents(),
),
_buildSkip()
],
),
);
}
Widget _buildContents() {
if (currentTarget == null) {
return SizedBox.shrink();
}
List<Widget> children = List();
TargetPosition target = getTargetCurrent(currentTarget);
var positioned = Offset(
target.offset.dx target.size.width / 2,
target.offset.dy target.size.height / 2,
);
double haloWidth;
double haloHeight;
if (currentTarget.shape == ShapeLightFocus.Circle) {
haloWidth = target.size.width > target.size.height
? target.size.width
: target.size.height;
haloHeight = haloWidth;
} else {
haloWidth = target.size.width;
haloHeight = target.size.height;
}
haloWidth = haloWidth * 0.6 widget.paddingFocus;
haloHeight = haloHeight * 0.6 widget.paddingFocus;
double weight = 0.0;
double top;
double bottom;
double left;
children = currentTarget.contents.map<Widget>((i) {
switch (i.align) {
case AlignContent.bottom:
{
weight = MediaQuery.of(context).size.width;
left = 0;
top = positioned.dy haloHeight;
bottom = null;
}
break;
case AlignContent.top:
{
weight = MediaQuery.of(context).size.width;
left = 0;
top = null;
bottom = haloHeight
(MediaQuery.of(context).size.height - positioned.dy);
}
break;
case AlignContent.left:
{
weight = positioned.dx - haloWidth;
left = 0;
top = positioned.dy - target.size.height / 2 - haloHeight;
bottom = null;
}
break;
case AlignContent.right:
{
left = positioned.dx haloWidth;
top = positioned.dy - target.size.height / 2 - haloHeight;
bottom = null;
weight = MediaQuery.of(context).size.width - left;
}
break;
case AlignContent.custom:
{
left = i.customPosition.left;
top = i.customPosition.top;
bottom = i.customPosition.bottom;
weight = MediaQuery.of(context).size.width;
}
break;
}
return Positioned(
top: top,
bottom: bottom,
left: left,
child: Container(
width: weight,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: i.child,
),
),
);
}).toList();
return Stack(
children: children,
);
}
Widget _buildSkip() {
if (widget.hideSkip) {
return SizedBox.shrink();
}
return Align(
alignment: widget.alignSkip,
child: SafeArea(
child: AnimatedOpacity(
opacity: showContent ? 1 : 0,
duration: Duration(milliseconds: 300),
child: InkWell(
onTap: widget.clickSkip,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
widget.textSkip,
style: widget.textStyleSkip,
key: textKey
),
),
),
),
),
);
}
void next() => _focusLightKey?.currentState?.next();
void previous() => _focusLightKey?.currentState?.previous();
}
это из-за того, что библиотека Coach Mark создает что-то вроде полноэкранного наложения, чтобы я не мог идентифицировать виджеты? если да, то что я мог сделать?
Комментарии:
1. можете ли вы добавить сюда свой код, чтобы мы могли просто скопировать вставку и проверить? нравится ваш метод сборки для отображения виджета и ваш текущий метод тестирования?
2. @ParthDave я обновил вопрос, пожалуйста, перепроверьте его
Ответ №1:
я нашел проблему здесь .. сам драйвер flutter синхронизирован по кадрам, поэтому в этой библиотеке COachMark мне приходится ждать, пока не останется ожидающих кадров..
я изменил свой код с этого
await world.driver.tap(finderHere);
чтобы быть чем-то вроде этого (я использую runUnsynchronyzed
, чтобы убедиться, что нет ожидающих фреймов)
await world.driver.runUnsynchronized(() async{
await world.driver.tap(finderHere);
});
Комментарии:
1. я просто проверял документацию для этого, поскольку это виджет наложения, а также думал о другом способе отключения анимации, я думаю, это может быть хорошо. позвольте мне проверить, android имеет флаг в build.gradle, но для flutter позвольте мне проверить
2. @ParthDave кстати, отключение анимации приведет к разрыву виджета?