Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
d2d07219
Unverified
Commit
d2d07219
authored
Oct 17, 2020
by
Marcin Jeleński
Committed by
GitHub
Oct 17, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Flutter Driver: command extensions and extension feature cleanup (#67916)
parent
4aa1154b
Changes
16
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
978 additions
and
533 deletions
+978
-533
driver_extension.dart
packages/flutter_driver/lib/driver_extension.dart
+2
-1
flutter_driver.dart
packages/flutter_driver/lib/flutter_driver.dart
+1
-0
create_finder_factory.dart
.../flutter_driver/lib/src/common/create_finder_factory.dart
+0
-117
deserialization_factory.dart
...lutter_driver/lib/src/common/deserialization_factory.dart
+70
-0
diagnostics_tree.dart
packages/flutter_driver/lib/src/common/diagnostics_tree.dart
+1
-0
find.dart
packages/flutter_driver/lib/src/common/find.dart
+1
-19
geometry.dart
packages/flutter_driver/lib/src/common/geometry.dart
+1
-0
gesture.dart
packages/flutter_driver/lib/src/common/gesture.dart
+1
-0
handler_factory.dart
packages/flutter_driver/lib/src/common/handler_factory.dart
+492
-0
text.dart
packages/flutter_driver/lib/src/common/text.dart
+1
-0
extension.dart
packages/flutter_driver/lib/src/extension/extension.dart
+200
-394
extension_test.dart
...es/flutter_driver/test/src/real_tests/extension_test.dart
+96
-0
find_test.dart
packages/flutter_driver/test/src/real_tests/find_test.dart
+1
-0
stub_command.dart
...lutter_driver/test/src/real_tests/stubs/stub_command.dart
+58
-0
stub_command_extension.dart
...ver/test/src/real_tests/stubs/stub_command_extension.dart
+51
-0
stub_finder_extension.dart
...iver/test/src/real_tests/stubs/stub_finder_extension.dart
+2
-2
No files found.
packages/flutter_driver/lib/driver_extension.dart
View file @
d2d07219
...
...
@@ -24,5 +24,6 @@
/// }
library
flutter_driver_extension
;
export
'src/common/create_finder_factory.dart'
;
export
'src/common/deserialization_factory.dart'
;
export
'src/common/handler_factory.dart'
;
export
'src/extension/extension.dart'
;
packages/flutter_driver/lib/flutter_driver.dart
View file @
d2d07219
...
...
@@ -13,6 +13,7 @@
/// Protractor (Angular), Espresso (Android) or Earl Gray (iOS).
library
flutter_driver
;
export
'src/common/deserialization_factory.dart'
;
export
'src/common/diagnostics_tree.dart'
;
export
'src/common/enum_util.dart'
;
export
'src/common/error.dart'
;
...
...
packages/flutter_driver/lib/src/common/create_finder_factory.dart
deleted
100644 → 0
View file @
4aa1154b
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/cupertino.dart'
;
import
'error.dart'
;
import
'find.dart'
;
/// A factory which creates [Finder]s from [SerializableFinder]s.
mixin
CreateFinderFactory
{
/// Creates the flutter widget finder from [SerializableFinder].
Finder
createFinder
(
SerializableFinder
finder
)
{
final
String
finderType
=
finder
.
finderType
;
switch
(
finderType
)
{
case
'ByText'
:
return
_createByTextFinder
(
finder
as
ByText
);
case
'ByTooltipMessage'
:
return
_createByTooltipMessageFinder
(
finder
as
ByTooltipMessage
);
case
'BySemanticsLabel'
:
return
_createBySemanticsLabelFinder
(
finder
as
BySemanticsLabel
);
case
'ByValueKey'
:
return
_createByValueKeyFinder
(
finder
as
ByValueKey
);
case
'ByType'
:
return
_createByTypeFinder
(
finder
as
ByType
);
case
'PageBack'
:
return
_createPageBackFinder
();
case
'Ancestor'
:
return
_createAncestorFinder
(
finder
as
Ancestor
);
case
'Descendant'
:
return
_createDescendantFinder
(
finder
as
Descendant
);
default
:
throw
DriverError
(
'Unsupported search specification type
$finderType
'
);
}
}
Finder
_createByTextFinder
(
ByText
arguments
)
{
return
find
.
text
(
arguments
.
text
);
}
Finder
_createByTooltipMessageFinder
(
ByTooltipMessage
arguments
)
{
return
find
.
byElementPredicate
((
Element
element
)
{
final
Widget
widget
=
element
.
widget
;
if
(
widget
is
Tooltip
)
{
return
widget
.
message
==
arguments
.
text
;
}
return
false
;
},
description:
'widget with text tooltip "
${arguments.text}
"'
);
}
Finder
_createBySemanticsLabelFinder
(
BySemanticsLabel
arguments
)
{
return
find
.
byElementPredicate
((
Element
element
)
{
if
(
element
is
!
RenderObjectElement
)
{
return
false
;
}
final
String
?
semanticsLabel
=
element
.
renderObject
.
debugSemantics
?.
label
;
if
(
semanticsLabel
==
null
)
{
return
false
;
}
final
Pattern
label
=
arguments
.
label
;
return
label
is
RegExp
?
label
.
hasMatch
(
semanticsLabel
)
:
label
==
semanticsLabel
;
},
description:
'widget with semantic label "
${arguments.label}
"'
);
}
Finder
_createByValueKeyFinder
(
ByValueKey
arguments
)
{
switch
(
arguments
.
keyValueType
)
{
case
'int'
:
return
find
.
byKey
(
ValueKey
<
int
>(
arguments
.
keyValue
as
int
));
case
'String'
:
return
find
.
byKey
(
ValueKey
<
String
>(
arguments
.
keyValue
as
String
));
default
:
throw
'Unsupported ByValueKey type:
${arguments.keyValueType}
'
;
}
}
Finder
_createByTypeFinder
(
ByType
arguments
)
{
return
find
.
byElementPredicate
((
Element
element
)
{
return
element
.
widget
.
runtimeType
.
toString
()
==
arguments
.
type
;
},
description:
'widget with runtimeType "
${arguments.type}
"'
);
}
Finder
_createPageBackFinder
()
{
return
find
.
byElementPredicate
((
Element
element
)
{
final
Widget
widget
=
element
.
widget
;
if
(
widget
is
Tooltip
)
{
return
widget
.
message
==
'Back'
;
}
if
(
widget
is
CupertinoNavigationBarBackButton
)
{
return
true
;
}
return
false
;
},
description:
'Material or Cupertino back button'
);
}
Finder
_createAncestorFinder
(
Ancestor
arguments
)
{
final
Finder
finder
=
find
.
ancestor
(
of:
createFinder
(
arguments
.
of
),
matching:
createFinder
(
arguments
.
matching
),
matchRoot:
arguments
.
matchRoot
,
);
return
arguments
.
firstMatchOnly
?
finder
.
first
:
finder
;
}
Finder
_createDescendantFinder
(
Descendant
arguments
)
{
final
Finder
finder
=
find
.
descendant
(
of:
createFinder
(
arguments
.
of
),
matching:
createFinder
(
arguments
.
matching
),
matchRoot:
arguments
.
matchRoot
,
);
return
arguments
.
firstMatchOnly
?
finder
.
first
:
finder
;
}
}
packages/flutter_driver/lib/src/common/deserialization_factory.dart
0 → 100644
View file @
d2d07219
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'diagnostics_tree.dart'
;
import
'error.dart'
;
import
'find.dart'
;
import
'frame_sync.dart'
;
import
'geometry.dart'
;
import
'gesture.dart'
;
import
'health.dart'
;
import
'layer_tree.dart'
;
import
'message.dart'
;
import
'render_tree.dart'
;
import
'request_data.dart'
;
import
'semantics.dart'
;
import
'text.dart'
;
import
'wait.dart'
;
/// A factory for deserializing [Finder]s.
mixin
DeserializeFinderFactory
{
/// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
SerializableFinder
deserializeFinder
(
Map
<
String
,
String
>
json
)
{
final
String
?
finderType
=
json
[
'finderType'
];
switch
(
finderType
)
{
case
'ByType'
:
return
ByType
.
deserialize
(
json
);
case
'ByValueKey'
:
return
ByValueKey
.
deserialize
(
json
);
case
'ByTooltipMessage'
:
return
ByTooltipMessage
.
deserialize
(
json
);
case
'BySemanticsLabel'
:
return
BySemanticsLabel
.
deserialize
(
json
);
case
'ByText'
:
return
ByText
.
deserialize
(
json
);
case
'PageBack'
:
return
const
PageBack
();
case
'Descendant'
:
return
Descendant
.
deserialize
(
json
,
this
);
case
'Ancestor'
:
return
Ancestor
.
deserialize
(
json
,
this
);
}
throw
DriverError
(
'Unsupported search specification type
$finderType
'
);
}
}
/// A factory for deserializing [Command]s.
mixin
DeserializeCommandFactory
{
/// Deserializes the finder from JSON generated by [Command.serialize] or [CommandWithTarget.serialize].
Command
deserializeCommand
(
Map
<
String
,
String
>
params
,
DeserializeFinderFactory
finderFactory
)
{
final
String
?
kind
=
params
[
'command'
];
switch
(
kind
)
{
case
'get_health'
:
return
GetHealth
.
deserialize
(
params
);
case
'get_layer_tree'
:
return
GetLayerTree
.
deserialize
(
params
);
case
'get_render_tree'
:
return
GetRenderTree
.
deserialize
(
params
);
case
'enter_text'
:
return
EnterText
.
deserialize
(
params
);
case
'get_text'
:
return
GetText
.
deserialize
(
params
,
finderFactory
);
case
'request_data'
:
return
RequestData
.
deserialize
(
params
);
case
'scroll'
:
return
Scroll
.
deserialize
(
params
,
finderFactory
);
case
'scrollIntoView'
:
return
ScrollIntoView
.
deserialize
(
params
,
finderFactory
);
case
'set_frame_sync'
:
return
SetFrameSync
.
deserialize
(
params
);
case
'set_semantics'
:
return
SetSemantics
.
deserialize
(
params
);
case
'set_text_entry_emulation'
:
return
SetTextEntryEmulation
.
deserialize
(
params
);
case
'tap'
:
return
Tap
.
deserialize
(
params
,
finderFactory
);
case
'waitFor'
:
return
WaitFor
.
deserialize
(
params
,
finderFactory
);
case
'waitForAbsent'
:
return
WaitForAbsent
.
deserialize
(
params
,
finderFactory
);
case
'waitForCondition'
:
return
WaitForCondition
.
deserialize
(
params
);
case
'waitUntilNoTransientCallbacks'
:
return
WaitUntilNoTransientCallbacks
.
deserialize
(
params
);
case
'waitUntilNoPendingFrame'
:
return
WaitUntilNoPendingFrame
.
deserialize
(
params
);
case
'waitUntilFirstFrameRasterized'
:
return
WaitUntilFirstFrameRasterized
.
deserialize
(
params
);
case
'get_semantics_id'
:
return
GetSemanticsId
.
deserialize
(
params
,
finderFactory
);
case
'get_offset'
:
return
GetOffset
.
deserialize
(
params
,
finderFactory
);
case
'get_diagnostics_tree'
:
return
GetDiagnosticsTree
.
deserialize
(
params
,
finderFactory
);
}
throw
DriverError
(
'Unsupported command kind
$kind
'
);
}
}
packages/flutter_driver/lib/src/common/diagnostics_tree.dart
View file @
d2d07219
...
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'deserialization_factory.dart'
;
import
'enum_util.dart'
;
import
'find.dart'
;
import
'message.dart'
;
...
...
packages/flutter_driver/lib/src/common/find.dart
View file @
d2d07219
...
...
@@ -6,28 +6,10 @@ import 'dart:convert';
import
'package:meta/meta.dart'
;
import
'deserialization_factory.dart'
;
import
'error.dart'
;
import
'message.dart'
;
/// A factory for deserializing [Finder]s.
mixin
DeserializeFinderFactory
{
/// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
SerializableFinder
deserializeFinder
(
Map
<
String
,
String
>
json
)
{
final
String
?
finderType
=
json
[
'finderType'
];
switch
(
finderType
)
{
case
'ByType'
:
return
ByType
.
deserialize
(
json
);
case
'ByValueKey'
:
return
ByValueKey
.
deserialize
(
json
);
case
'ByTooltipMessage'
:
return
ByTooltipMessage
.
deserialize
(
json
);
case
'BySemanticsLabel'
:
return
BySemanticsLabel
.
deserialize
(
json
);
case
'ByText'
:
return
ByText
.
deserialize
(
json
);
case
'PageBack'
:
return
const
PageBack
();
case
'Descendant'
:
return
Descendant
.
deserialize
(
json
,
this
);
case
'Ancestor'
:
return
Ancestor
.
deserialize
(
json
,
this
);
}
throw
DriverError
(
'Unsupported search specification type
$finderType
'
);
}
}
const
List
<
Type
>
_supportedKeyValueTypes
=
<
Type
>[
String
,
int
];
DriverError
_createInvalidKeyValueTypeError
(
String
invalidType
)
{
...
...
packages/flutter_driver/lib/src/common/geometry.dart
View file @
d2d07219
...
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'deserialization_factory.dart'
;
import
'enum_util.dart'
;
import
'find.dart'
;
import
'message.dart'
;
...
...
packages/flutter_driver/lib/src/common/gesture.dart
View file @
d2d07219
...
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'deserialization_factory.dart'
;
import
'find.dart'
;
import
'message.dart'
;
...
...
packages/flutter_driver/lib/src/common/handler_factory.dart
0 → 100644
View file @
d2d07219
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_driver/driver_extension.dart'
;
import
'package:flutter_driver/src/extension/wait_conditions.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'diagnostics_tree.dart'
;
import
'error.dart'
;
import
'find.dart'
;
import
'frame_sync.dart'
;
import
'geometry.dart'
;
import
'gesture.dart'
;
import
'health.dart'
;
import
'layer_tree.dart'
;
import
'message.dart'
;
import
'render_tree.dart'
;
import
'request_data.dart'
;
import
'semantics.dart'
;
import
'text.dart'
;
import
'wait.dart'
;
/// A factory which creates [Finder]s from [SerializableFinder]s.
mixin
CreateFinderFactory
{
/// Creates the flutter widget finder from [SerializableFinder].
Finder
createFinder
(
SerializableFinder
finder
)
{
final
String
finderType
=
finder
.
finderType
;
switch
(
finderType
)
{
case
'ByText'
:
return
_createByTextFinder
(
finder
as
ByText
);
case
'ByTooltipMessage'
:
return
_createByTooltipMessageFinder
(
finder
as
ByTooltipMessage
);
case
'BySemanticsLabel'
:
return
_createBySemanticsLabelFinder
(
finder
as
BySemanticsLabel
);
case
'ByValueKey'
:
return
_createByValueKeyFinder
(
finder
as
ByValueKey
);
case
'ByType'
:
return
_createByTypeFinder
(
finder
as
ByType
);
case
'PageBack'
:
return
_createPageBackFinder
();
case
'Ancestor'
:
return
_createAncestorFinder
(
finder
as
Ancestor
);
case
'Descendant'
:
return
_createDescendantFinder
(
finder
as
Descendant
);
default
:
throw
DriverError
(
'Unsupported search specification type
$finderType
'
);
}
}
Finder
_createByTextFinder
(
ByText
arguments
)
{
return
find
.
text
(
arguments
.
text
);
}
Finder
_createByTooltipMessageFinder
(
ByTooltipMessage
arguments
)
{
return
find
.
byElementPredicate
((
Element
element
)
{
final
Widget
widget
=
element
.
widget
;
if
(
widget
is
Tooltip
)
{
return
widget
.
message
==
arguments
.
text
;
}
return
false
;
},
description:
'widget with text tooltip "
${arguments.text}
"'
);
}
Finder
_createBySemanticsLabelFinder
(
BySemanticsLabel
arguments
)
{
return
find
.
byElementPredicate
((
Element
element
)
{
if
(
element
is
!
RenderObjectElement
)
{
return
false
;
}
final
String
?
semanticsLabel
=
element
.
renderObject
.
debugSemantics
?.
label
;
if
(
semanticsLabel
==
null
)
{
return
false
;
}
final
Pattern
label
=
arguments
.
label
;
return
label
is
RegExp
?
label
.
hasMatch
(
semanticsLabel
)
:
label
==
semanticsLabel
;
},
description:
'widget with semantic label "
${arguments.label}
"'
);
}
Finder
_createByValueKeyFinder
(
ByValueKey
arguments
)
{
switch
(
arguments
.
keyValueType
)
{
case
'int'
:
return
find
.
byKey
(
ValueKey
<
int
>(
arguments
.
keyValue
as
int
));
case
'String'
:
return
find
.
byKey
(
ValueKey
<
String
>(
arguments
.
keyValue
as
String
));
default
:
throw
'Unsupported ByValueKey type:
${arguments.keyValueType}
'
;
}
}
Finder
_createByTypeFinder
(
ByType
arguments
)
{
return
find
.
byElementPredicate
((
Element
element
)
{
return
element
.
widget
.
runtimeType
.
toString
()
==
arguments
.
type
;
},
description:
'widget with runtimeType "
${arguments.type}
"'
);
}
Finder
_createPageBackFinder
()
{
return
find
.
byElementPredicate
((
Element
element
)
{
final
Widget
widget
=
element
.
widget
;
if
(
widget
is
Tooltip
)
{
return
widget
.
message
==
'Back'
;
}
if
(
widget
is
CupertinoNavigationBarBackButton
)
{
return
true
;
}
return
false
;
},
description:
'Material or Cupertino back button'
);
}
Finder
_createAncestorFinder
(
Ancestor
arguments
)
{
final
Finder
finder
=
find
.
ancestor
(
of:
createFinder
(
arguments
.
of
),
matching:
createFinder
(
arguments
.
matching
),
matchRoot:
arguments
.
matchRoot
,
);
return
arguments
.
firstMatchOnly
?
finder
.
first
:
finder
;
}
Finder
_createDescendantFinder
(
Descendant
arguments
)
{
final
Finder
finder
=
find
.
descendant
(
of:
createFinder
(
arguments
.
of
),
matching:
createFinder
(
arguments
.
matching
),
matchRoot:
arguments
.
matchRoot
,
);
return
arguments
.
firstMatchOnly
?
finder
.
first
:
finder
;
}
}
/// A factory for [Command] handlers.
mixin
CommandHandlerFactory
{
/// With [_frameSync] enabled, Flutter Driver will wait to perform an action
/// until there are no pending frames in the app under test.
bool
_frameSync
=
true
;
/// Gets [DataHandler] for result delivery.
@protected
DataHandler
?
getDataHandler
()
=>
null
;
/// Registers text input emulation.
@protected
void
registerTextInput
()
{
_testTextInput
.
register
();
}
final
TestTextInput
_testTextInput
=
TestTextInput
();
/// Deserializes the finder from JSON generated by [Command.serialize] or [CommandWithTarget.serialize].
Future
<
Result
?>
handleCommand
(
Command
command
,
WidgetController
prober
,
CreateFinderFactory
finderFactory
)
{
switch
(
command
.
kind
)
{
case
'get_health'
:
return
_getHealth
(
command
);
case
'get_layer_tree'
:
return
_getLayerTree
(
command
);
case
'get_render_tree'
:
return
_getRenderTree
(
command
);
case
'enter_text'
:
return
_enterText
(
command
);
case
'get_text'
:
return
_getText
(
command
,
finderFactory
);
case
'request_data'
:
return
_requestData
(
command
);
case
'scroll'
:
return
_scroll
(
command
,
prober
,
finderFactory
);
case
'scrollIntoView'
:
return
_scrollIntoView
(
command
,
finderFactory
);
case
'set_frame_sync'
:
return
_setFrameSync
(
command
);
case
'set_semantics'
:
return
_setSemantics
(
command
);
case
'set_text_entry_emulation'
:
return
_setTextEntryEmulation
(
command
);
case
'tap'
:
return
_tap
(
command
,
prober
,
finderFactory
);
case
'waitFor'
:
return
_waitFor
(
command
,
finderFactory
);
case
'waitForAbsent'
:
return
_waitForAbsent
(
command
,
finderFactory
);
case
'waitForCondition'
:
return
_waitForCondition
(
command
);
case
'waitUntilNoTransientCallbacks'
:
return
_waitUntilNoTransientCallbacks
(
command
);
case
'waitUntilNoPendingFrame'
:
return
_waitUntilNoPendingFrame
(
command
);
case
'waitUntilFirstFrameRasterized'
:
return
_waitUntilFirstFrameRasterized
(
command
);
case
'get_semantics_id'
:
return
_getSemanticsId
(
command
,
finderFactory
);
case
'get_offset'
:
return
_getOffset
(
command
,
finderFactory
);
case
'get_diagnostics_tree'
:
return
_getDiagnosticsTree
(
command
,
finderFactory
);
}
throw
DriverError
(
'Unsupported command kind
${command.kind}
'
);
}
Future
<
Health
>
_getHealth
(
Command
command
)
async
=>
const
Health
(
HealthStatus
.
ok
);
Future
<
LayerTree
>
_getLayerTree
(
Command
command
)
async
{
return
LayerTree
(
RendererBinding
.
instance
?.
renderView
.
debugLayer
?.
toStringDeep
());
}
Future
<
RenderTree
>
_getRenderTree
(
Command
command
)
async
{
return
RenderTree
(
RendererBinding
.
instance
?.
renderView
.
toStringDeep
());
}
Future
<
EnterTextResult
>
_enterText
(
Command
command
)
async
{
if
(!
_testTextInput
.
isRegistered
)
{
throw
'Unable to fulfill `FlutterDriver.enterText`. Text emulation is '
'disabled. You can enable it using `FlutterDriver.setTextEntryEmulation`.'
;
}
final
EnterText
enterTextCommand
=
command
as
EnterText
;
_testTextInput
.
enterText
(
enterTextCommand
.
text
);
return
const
EnterTextResult
();
}
Future
<
RequestDataResult
>
_requestData
(
Command
command
)
async
{
final
RequestData
requestDataCommand
=
command
as
RequestData
;
final
DataHandler
?
dataHandler
=
getDataHandler
();
return
RequestDataResult
(
dataHandler
==
null
?
'No requestData Extension registered'
:
await
dataHandler
(
requestDataCommand
.
message
));
}
Future
<
SetFrameSyncResult
>
_setFrameSync
(
Command
command
)
async
{
final
SetFrameSync
setFrameSyncCommand
=
command
as
SetFrameSync
;
_frameSync
=
setFrameSyncCommand
.
enabled
;
return
const
SetFrameSyncResult
();
}
Future
<
TapResult
>
_tap
(
Command
command
,
WidgetController
prober
,
CreateFinderFactory
finderFactory
)
async
{
final
Tap
tapCommand
=
command
as
Tap
;
final
Finder
computedFinder
=
await
waitForElement
(
finderFactory
.
createFinder
(
tapCommand
.
finder
).
hitTestable
(),
);
await
prober
.
tap
(
computedFinder
);
return
const
TapResult
();
}
Future
<
WaitForResult
>
_waitFor
(
Command
command
,
CreateFinderFactory
finderFactory
)
async
{
final
WaitFor
waitForCommand
=
command
as
WaitFor
;
await
waitForElement
(
finderFactory
.
createFinder
(
waitForCommand
.
finder
));
return
const
WaitForResult
();
}
Future
<
WaitForAbsentResult
>
_waitForAbsent
(
Command
command
,
CreateFinderFactory
finderFactory
)
async
{
final
WaitForAbsent
waitForAbsentCommand
=
command
as
WaitForAbsent
;
await
waitForAbsentElement
(
finderFactory
.
createFinder
(
waitForAbsentCommand
.
finder
));
return
const
WaitForAbsentResult
();
}
Future
<
Result
?>
_waitForCondition
(
Command
command
)
async
{
assert
(
command
!=
null
);
final
WaitForCondition
waitForConditionCommand
=
command
as
WaitForCondition
;
final
WaitCondition
condition
=
deserializeCondition
(
waitForConditionCommand
.
condition
);
await
condition
.
wait
();
return
null
;
}
@Deprecated
(
'This method has been deprecated in favor of _waitForCondition. '
'This feature was deprecated after v1.9.3.'
)
Future
<
Result
?>
_waitUntilNoTransientCallbacks
(
Command
command
)
async
{
if
(
SchedulerBinding
.
instance
!.
transientCallbackCount
!=
0
)
await
_waitUntilFrame
(()
=>
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
);
return
null
;
}
/// Returns a future that waits until no pending frame is scheduled (frame is synced).
///
/// Specifically, it checks:
/// * Whether the count of transient callbacks is zero.
/// * Whether there's no pending request for scheduling a new frame.
///
/// We consider the frame is synced when both conditions are met.
///
/// This method relies on a Flutter Driver mechanism called "frame sync",
/// which waits for transient animations to finish. Persistent animations will
/// cause this to wait forever.
///
/// If a test needs to interact with the app while animations are running, it
/// should avoid this method and instead disable the frame sync using
/// `set_frame_sync` method. See [FlutterDriver.runUnsynchronized] for more
/// details on how to do this. Note, disabling frame sync will require the
/// test author to use some other method to avoid flakiness.
///
/// This method has been deprecated in favor of [_waitForCondition].
@Deprecated
(
'This method has been deprecated in favor of _waitForCondition. '
'This feature was deprecated after v1.9.3.'
)
Future
<
Result
?>
_waitUntilNoPendingFrame
(
Command
command
)
async
{
await
_waitUntilFrame
(()
{
return
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
&&
!
SchedulerBinding
.
instance
!.
hasScheduledFrame
;
});
return
null
;
}
Future
<
GetSemanticsIdResult
>
_getSemanticsId
(
Command
command
,
CreateFinderFactory
finderFactory
)
async
{
final
GetSemanticsId
semanticsCommand
=
command
as
GetSemanticsId
;
final
Finder
target
=
await
waitForElement
(
finderFactory
.
createFinder
(
semanticsCommand
.
finder
));
final
Iterable
<
Element
>
elements
=
target
.
evaluate
();
if
(
elements
.
length
>
1
)
{
throw
StateError
(
'Found more than one element with the same ID:
$elements
'
);
}
final
Element
element
=
elements
.
single
;
RenderObject
?
renderObject
=
element
.
renderObject
;
SemanticsNode
?
node
;
while
(
renderObject
!=
null
&&
node
==
null
)
{
node
=
renderObject
.
debugSemantics
;
renderObject
=
renderObject
.
parent
as
RenderObject
?;
}
if
(
node
==
null
)
throw
StateError
(
'No semantics data found'
);
return
GetSemanticsIdResult
(
node
.
id
);
}
Future
<
GetOffsetResult
>
_getOffset
(
Command
command
,
CreateFinderFactory
finderFactory
)
async
{
final
GetOffset
getOffsetCommand
=
command
as
GetOffset
;
final
Finder
finder
=
await
waitForElement
(
finderFactory
.
createFinder
(
getOffsetCommand
.
finder
));
final
Element
element
=
finder
.
evaluate
().
single
;
final
RenderBox
box
=
(
element
.
renderObject
as
RenderBox
?)!;
Offset
localPoint
;
switch
(
getOffsetCommand
.
offsetType
)
{
case
OffsetType
.
topLeft
:
localPoint
=
Offset
.
zero
;
break
;
case
OffsetType
.
topRight
:
localPoint
=
box
.
size
.
topRight
(
Offset
.
zero
);
break
;
case
OffsetType
.
bottomLeft
:
localPoint
=
box
.
size
.
bottomLeft
(
Offset
.
zero
);
break
;
case
OffsetType
.
bottomRight
:
localPoint
=
box
.
size
.
bottomRight
(
Offset
.
zero
);
break
;
case
OffsetType
.
center
:
localPoint
=
box
.
size
.
center
(
Offset
.
zero
);
break
;
}
final
Offset
globalPoint
=
box
.
localToGlobal
(
localPoint
);
return
GetOffsetResult
(
dx:
globalPoint
.
dx
,
dy:
globalPoint
.
dy
);
}
Future
<
DiagnosticsTreeResult
>
_getDiagnosticsTree
(
Command
command
,
CreateFinderFactory
finderFactory
)
async
{
final
GetDiagnosticsTree
diagnosticsCommand
=
command
as
GetDiagnosticsTree
;
final
Finder
finder
=
await
waitForElement
(
finderFactory
.
createFinder
(
diagnosticsCommand
.
finder
));
final
Element
element
=
finder
.
evaluate
().
single
;
DiagnosticsNode
diagnosticsNode
;
switch
(
diagnosticsCommand
.
diagnosticsType
)
{
case
DiagnosticsType
.
renderObject
:
diagnosticsNode
=
element
.
renderObject
!.
toDiagnosticsNode
();
break
;
case
DiagnosticsType
.
widget
:
diagnosticsNode
=
element
.
toDiagnosticsNode
();
break
;
}
return
DiagnosticsTreeResult
(
diagnosticsNode
.
toJsonMap
(
DiagnosticsSerializationDelegate
(
subtreeDepth:
diagnosticsCommand
.
subtreeDepth
,
includeProperties:
diagnosticsCommand
.
includeProperties
,
)));
}
Future
<
ScrollResult
>
_scroll
(
Command
command
,
WidgetController
_prober
,
CreateFinderFactory
finderFactory
)
async
{
final
Scroll
scrollCommand
=
command
as
Scroll
;
final
Finder
target
=
await
waitForElement
(
finderFactory
.
createFinder
(
scrollCommand
.
finder
));
final
int
totalMoves
=
scrollCommand
.
duration
.
inMicroseconds
*
scrollCommand
.
frequency
~/
Duration
.
microsecondsPerSecond
;
final
Offset
delta
=
Offset
(
scrollCommand
.
dx
,
scrollCommand
.
dy
)
/
totalMoves
.
toDouble
();
final
Duration
pause
=
scrollCommand
.
duration
~/
totalMoves
;
final
Offset
startLocation
=
_prober
.
getCenter
(
target
);
Offset
currentLocation
=
startLocation
;
final
TestPointer
pointer
=
TestPointer
(
1
);
_prober
.
binding
.
handlePointerEvent
(
pointer
.
down
(
startLocation
));
await
Future
<
void
>.
value
();
// so that down and move don't happen in the same microtask
for
(
int
moves
=
0
;
moves
<
totalMoves
;
moves
+=
1
)
{
currentLocation
=
currentLocation
+
delta
;
_prober
.
binding
.
handlePointerEvent
(
pointer
.
move
(
currentLocation
));
await
Future
<
void
>.
delayed
(
pause
);
}
_prober
.
binding
.
handlePointerEvent
(
pointer
.
up
());
return
const
ScrollResult
();
}
Future
<
ScrollResult
>
_scrollIntoView
(
Command
command
,
CreateFinderFactory
finderFactory
)
async
{
final
ScrollIntoView
scrollIntoViewCommand
=
command
as
ScrollIntoView
;
final
Finder
target
=
await
waitForElement
(
finderFactory
.
createFinder
(
scrollIntoViewCommand
.
finder
));
await
Scrollable
.
ensureVisible
(
target
.
evaluate
().
single
,
duration:
const
Duration
(
milliseconds:
100
),
alignment:
scrollIntoViewCommand
.
alignment
);
return
const
ScrollResult
();
}
Future
<
GetTextResult
>
_getText
(
Command
command
,
CreateFinderFactory
finderFactory
)
async
{
final
GetText
getTextCommand
=
command
as
GetText
;
final
Finder
target
=
await
waitForElement
(
finderFactory
.
createFinder
(
getTextCommand
.
finder
));
final
Widget
widget
=
target
.
evaluate
().
single
.
widget
;
String
?
text
;
if
(
widget
.
runtimeType
==
Text
)
{
text
=
(
widget
as
Text
).
data
;
}
else
if
(
widget
.
runtimeType
==
RichText
)
{
final
RichText
richText
=
widget
as
RichText
;
if
(
richText
.
text
.
runtimeType
==
TextSpan
)
{
text
=
(
richText
.
text
as
TextSpan
).
text
;
}
}
else
if
(
widget
.
runtimeType
==
TextField
)
{
text
=
(
widget
as
TextField
).
controller
?.
text
;
}
else
if
(
widget
.
runtimeType
==
TextFormField
)
{
text
=
(
widget
as
TextFormField
).
controller
?.
text
;
}
else
if
(
widget
.
runtimeType
==
EditableText
)
{
text
=
(
widget
as
EditableText
).
controller
.
text
;
}
if
(
text
==
null
)
{
throw
UnsupportedError
(
'Type
${widget.runtimeType.toString()}
is currently not supported by getText'
);
}
return
GetTextResult
(
text
);
}
Future
<
SetTextEntryEmulationResult
>
_setTextEntryEmulation
(
Command
command
)
async
{
final
SetTextEntryEmulation
setTextEntryEmulationCommand
=
command
as
SetTextEntryEmulation
;
if
(
setTextEntryEmulationCommand
.
enabled
)
{
_testTextInput
.
register
();
}
else
{
_testTextInput
.
unregister
();
}
return
const
SetTextEntryEmulationResult
();
}
SemanticsHandle
?
_semantics
;
bool
get
_semanticsIsEnabled
=>
RendererBinding
.
instance
!.
pipelineOwner
.
semanticsOwner
!=
null
;
Future
<
SetSemanticsResult
>
_setSemantics
(
Command
command
)
async
{
final
SetSemantics
setSemanticsCommand
=
command
as
SetSemantics
;
final
bool
semanticsWasEnabled
=
_semanticsIsEnabled
;
if
(
setSemanticsCommand
.
enabled
&&
_semantics
==
null
)
{
_semantics
=
RendererBinding
.
instance
!.
pipelineOwner
.
ensureSemantics
();
if
(!
semanticsWasEnabled
)
{
// wait for the first frame where semantics is enabled.
final
Completer
<
void
>
completer
=
Completer
<
void
>();
SchedulerBinding
.
instance
!.
addPostFrameCallback
((
Duration
d
)
{
completer
.
complete
();
});
await
completer
.
future
;
}
}
else
if
(!
setSemanticsCommand
.
enabled
&&
_semantics
!=
null
)
{
_semantics
!.
dispose
();
_semantics
=
null
;
}
return
SetSemanticsResult
(
semanticsWasEnabled
!=
_semanticsIsEnabled
);
}
// This can be used to wait for the first frame being rasterized during app launch.
@Deprecated
(
'This method has been deprecated in favor of _waitForCondition. '
'This feature was deprecated after v1.9.3.'
)
Future
<
Result
?>
_waitUntilFirstFrameRasterized
(
Command
command
)
async
{
await
WidgetsBinding
.
instance
!.
waitUntilFirstFrameRasterized
;
return
null
;
}
/// Runs `finder` repeatedly until it finds one or more [Element]s.
Future
<
Finder
>
waitForElement
(
Finder
finder
)
async
{
if
(
_frameSync
)
await
_waitUntilFrame
(()
=>
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
);
await
_waitUntilFrame
(()
=>
finder
.
evaluate
().
isNotEmpty
);
if
(
_frameSync
)
await
_waitUntilFrame
(()
=>
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
);
return
finder
;
}
/// Runs `finder` repeatedly until it finds zero [Element]s.
Future
<
Finder
>
waitForAbsentElement
(
Finder
finder
)
async
{
if
(
_frameSync
)
await
_waitUntilFrame
(()
=>
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
);
await
_waitUntilFrame
(()
=>
finder
.
evaluate
().
isEmpty
);
if
(
_frameSync
)
await
_waitUntilFrame
(()
=>
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
);
return
finder
;
}
// Waits until at the end of a frame the provided [condition] is [true].
Future
<
void
>
_waitUntilFrame
(
bool
condition
(),
[
Completer
<
void
>?
completer
])
{
completer
??=
Completer
<
void
>();
if
(!
condition
())
{
SchedulerBinding
.
instance
!.
addPostFrameCallback
((
Duration
timestamp
)
{
_waitUntilFrame
(
condition
,
completer
);
});
}
else
{
completer
.
complete
();
}
return
completer
.
future
;
}
}
packages/flutter_driver/lib/src/common/text.dart
View file @
d2d07219
...
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'deserialization_factory.dart'
;
import
'find.dart'
;
import
'message.dart'
;
...
...
packages/flutter_driver/lib/src/extension/extension.dart
View file @
d2d07219
...
...
@@ -9,33 +9,21 @@ import 'package:flutter/cupertino.dart';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
show
RendererBinding
,
SemanticsHandle
;
import
'package:flutter/rendering.dart'
show
RendererBinding
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/semantics.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'../common/create_finder_factory.dart'
;
import
'../common/diagnostics_tree.dart'
;
import
'../common/deserialization_factory.dart'
;
import
'../common/error.dart'
;
import
'../common/find.dart'
;
import
'../common/frame_sync.dart'
;
import
'../common/geometry.dart'
;
import
'../common/gesture.dart'
;
import
'../common/health.dart'
;
import
'../common/layer_tree.dart'
;
import
'../common/handler_factory.dart'
;
import
'../common/message.dart'
;
import
'../common/render_tree.dart'
;
import
'../common/request_data.dart'
;
import
'../common/semantics.dart'
;
import
'../common/text.dart'
;
import
'../common/wait.dart'
;
import
'_extension_io.dart'
if
(
dart
.
library
.
html
)
'_extension_web.dart'
;
import
'wait_conditions.dart'
;
const
String
_extensionMethodName
=
'driver'
;
const
String
_extensionMethod
=
'ext.flutter.
$_extensionMethodName
'
;
/// Signature for the handler passed to [enableFlutterDriverExtension].
///
...
...
@@ -44,16 +32,17 @@ const String _extensionMethod = 'ext.flutter.$_extensionMethodName';
typedef
DataHandler
=
Future
<
String
>
Function
(
String
?
message
);
class
_DriverBinding
extends
BindingBase
with
SchedulerBinding
,
ServicesBinding
,
GestureBinding
,
PaintingBinding
,
SemanticsBinding
,
RendererBinding
,
WidgetsBinding
{
_DriverBinding
(
this
.
_handler
,
this
.
_silenceErrors
,
this
.
finders
);
_DriverBinding
(
this
.
_handler
,
this
.
_silenceErrors
,
this
.
finders
,
this
.
commands
);
final
DataHandler
?
_handler
;
final
bool
_silenceErrors
;
final
List
<
FinderExtension
>?
finders
;
final
List
<
CommandExtension
>?
commands
;
@override
void
initServiceExtensions
()
{
super
.
initServiceExtensions
();
final
FlutterDriverExtension
extension
=
FlutterDriverExtension
(
_handler
,
_silenceErrors
,
finders:
finders
??
const
<
FinderExtension
>[]);
final
FlutterDriverExtension
extension
=
FlutterDriverExtension
(
_handler
,
_silenceErrors
,
finders:
finders
??
const
<
FinderExtension
>[]
,
commands:
commands
??
const
<
CommandExtension
>[]
);
registerServiceExtension
(
name:
_extensionMethodName
,
callback:
extension
.
call
,
...
...
@@ -89,24 +78,36 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding,
/// will still be returned in the `response` field of the result JSON along
/// with an `isError` boolean.
///
/// The `finders` parameter are used to add custom finders, as in the following example.
/// The `finders` and `commands` parameters are optional and used to add custom
/// finders or commands, as in the following example.
///
/// ```dart main
/// void main() {
/// enableFlutterDriverExtension(finders: <FinderExtension>[ SomeFinderExtension() ]);
/// enableFlutterDriverExtension(
/// finders: <FinderExtension>[ SomeFinderExtension() ],
/// commands: <CommandExtension>[ SomeCommandExtension() ],
/// );
///
/// app.main();
/// }
/// ```
///
/// ```dart
/// class Some extends SerializableFinder {
/// const Some(this.title);
/// driver.sendCommand(SomeCommand(ByValueKey('Button'), 7));
/// ```
///
/// Note: SomeFinder and SomeFinderExtension must be placed in different files
/// to avoid `dart:ui` import issue. Imports relative to `dart:ui` can't be
/// accessed from host runner, where flutter runtime is not accessible.
///
/// ```dart
/// class SomeFinder extends SerializableFinder {
/// const SomeFinder(this.title);
///
/// final String title;
///
/// @override
/// String get finderType => 'Some';
/// String get finderType => 'Some
Finder
';
///
/// @override
/// Map<String, String> serialize() => super.serialize()..addAll(<String, String>{
...
...
@@ -118,14 +119,14 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding,
/// ```dart
/// class SomeFinderExtension extends FinderExtension {
///
/// String get finderType => 'Some';
/// String get finderType => 'Some
Finder
';
///
/// SerializableFinder deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory) {
/// return Some(json['title']);
/// return Some
Finder
(json['title']);
/// }
///
/// Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory) {
/// Some someFinder = finder as Some;
/// Some someFinder = finder as Some
Finder
;
///
/// return find.byElementPredicate((Element element) {
/// final Widget widget = element.widget;
...
...
@@ -138,9 +139,87 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding,
/// }
/// ```
///
void
enableFlutterDriverExtension
(
{
DataHandler
?
handler
,
bool
silenceErrors
=
false
,
List
<
FinderExtension
>?
finders
})
{
/// Note: SomeCommand, SomeResult and SomeCommandExtension must be placed in
/// different files to avoid `dart:ui` import issue. Imports relative to `dart:ui`
/// can't be accessed from host runner, where flutter runtime is not accessible.
///
/// ```dart
/// class SomeCommand extends CommandWithTarget {
/// SomeCommand(SerializableFinder finder, this.times, {Duration? timeout})
/// : super(finder, timeout: timeout);
///
/// SomeCommand.deserialize(Map<String, String> json, DeserializeFinderFactory finderFactory)
/// : times = int.parse(json['times']!),
/// super.deserialize(json, finderFactory);
///
/// @override
/// Map<String, String> serialize() {
/// return super.serialize()..addAll(<String, String>{'times': '$times'});
/// }
///
/// @override
/// String get kind => 'SomeCommand';
///
/// final int times;
/// }
///```
///
/// ```dart
/// class SomeCommandResult extends Result {
/// const SomeCommandResult(this.resultParam);
///
/// final String resultParam;
///
/// @override
/// Map<String, dynamic> toJson() {
/// return <String, dynamic>{
/// 'resultParam': resultParam,
/// };
/// }
/// }
/// ```
///
/// ```dart
/// class SomeCommandExtension extends CommandExtension {
/// @override
/// String get commandKind => 'SomeCommand';
///
/// @override
/// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
/// final SomeCommand someCommand = command as SomeCommand;
///
/// // Deserialize [Finder]:
/// final Finder finder = finderFactory.createFinder(stubCommand.finder);
///
/// // Wait for [Element]:
/// handlerFactory.waitForElement(finder);
///
/// // Alternatively, wait for [Element] absence:
/// handlerFactory.waitForAbsentElement(finder);
///
/// // Submit known [Command]s:
/// for (int index = 0; i < someCommand.times; index++) {
/// await handlerFactory.handleCommand(Tap(someCommand.finder), prober, finderFactory);
/// }
///
/// // Alternatively, use [WidgetController]:
/// for (int index = 0; i < stubCommand.times; index++) {
/// await prober.tap(finder);
/// }
///
/// return const SomeCommandResult('foo bar');
/// }
///
/// @override
/// Command deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory) {
/// return SomeCommand.deserialize(params, finderFactory);
/// }
/// }
/// ```
///
void
enableFlutterDriverExtension
(
{
DataHandler
?
handler
,
bool
silenceErrors
=
false
,
List
<
FinderExtension
>?
finders
,
List
<
CommandExtension
>?
commands
})
{
assert
(
WidgetsBinding
.
instance
==
null
);
_DriverBinding
(
handler
,
silenceErrors
,
finders
??
<
FinderExtension
>[]);
_DriverBinding
(
handler
,
silenceErrors
,
finders
??
<
FinderExtension
>[]
,
commands
??
<
CommandExtension
>[]
);
assert
(
WidgetsBinding
.
instance
is
_DriverBinding
);
}
...
...
@@ -150,107 +229,119 @@ typedef CommandHandlerCallback = Future<Result?> Function(Command c);
/// Signature for functions that deserialize a JSON map to a command object.
typedef
CommandDeserializerCallback
=
Command
Function
(
Map
<
String
,
String
>
params
);
/// Used to expand the new
Finder
/// Used to expand the new
[Finder].
abstract
class
FinderExtension
{
/// Identifies the type of finder to be used by the driver extension.
String
get
finderType
;
/// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
/// [finderFactory] could be used to deserialize nested finders.
///
/// Use [finderFactory] to deserialize nested [Finder]s.
///
/// See also:
/// * [Ancestor], a finder that uses other [Finder]s as parameters.
SerializableFinder
deserialize
(
Map
<
String
,
String
>
params
,
DeserializeFinderFactory
finderFactory
);
/// Signature for functions that run the given finder and return the [Element]
/// found, if any, or null otherwise.
/// [finderFactory] could be used to create nested finders.
///
/// Call [finderFactory] to create known, nested [Finder]s from [SerializableFinder]s.
Finder
createFinder
(
SerializableFinder
finder
,
CreateFinderFactory
finderFactory
);
}
/// Used to expand the new [Command].
///
/// See also:
/// * [CommandWithTarget], a base class for [Command]s with [Finder]s.
abstract
class
CommandExtension
{
/// Identifies the type of command to be used by the driver extension.
String
get
commandKind
;
/// Deserializes the command from JSON generated by [Command.serialize].
///
/// Use [finderFactory] to deserialize nested [Finder]s.
/// Usually used for [CommandWithTarget]s.
///
/// Call [commandFactory] to deserialize commands specified as parameters.
///
/// See also:
/// * [CommandWithTarget], a base class for commands with target finders.
/// * [Tap], a command that uses [Finder]s as parameter.
Command
deserialize
(
Map
<
String
,
String
>
params
,
DeserializeFinderFactory
finderFactory
,
DeserializeCommandFactory
commandFactory
);
/// Calls action for given [command].
/// Returns action [Result].
/// Invoke [prober] functions to perform widget actions.
/// Use [finderFactory] to create [Finder]s from [SerializableFinder].
/// Call [handlerFactory] to invoke other [Command]s or [CommandWithTarget]s.
///
/// The following example shows invoking nested command with [handlerFactory].
///
/// ```dart
/// @override
/// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
/// final StubNestedCommand stubCommand = command as StubNestedCommand;
/// for (int index = 0; i < stubCommand.times; index++) {
/// await handlerFactory.handleCommand(Tap(stubCommand.finder), prober, finderFactory);
/// }
/// return const StubCommandResult('stub response');
/// }
/// ```
///
/// Check the example below for direct [WidgetController] usage with [prober]:
///
/// ```dart
/// @override
/// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
/// final StubProberCommand stubCommand = command as StubProberCommand;
/// for (int index = 0; i < stubCommand.times; index++) {
/// await prober.tap(finderFactory.createFinder(stubCommand.finder));
/// }
/// return const StubCommandResult('stub response');
/// }
/// ```
Future
<
Result
>
call
(
Command
command
,
WidgetController
prober
,
CreateFinderFactory
finderFactory
,
CommandHandlerFactory
handlerFactory
);
}
/// The class that manages communication between a Flutter Driver test and the
/// application being remote-controlled, on the application side.
///
/// This is not normally used directly. It is instantiated automatically when
/// calling [enableFlutterDriverExtension].
@visibleForTesting
class
FlutterDriverExtension
with
DeserializeFinderFactory
,
CreateFinderFactory
{
class
FlutterDriverExtension
with
DeserializeFinderFactory
,
CreateFinderFactory
,
DeserializeCommandFactory
,
CommandHandlerFactory
{
/// Creates an object to manage a Flutter Driver connection.
FlutterDriverExtension
(
this
.
_requestDataHandler
,
this
.
_silenceErrors
,
{
List
<
FinderExtension
>
finders
=
const
<
FinderExtension
>[],
List
<
CommandExtension
>
commands
=
const
<
CommandExtension
>[],
})
:
assert
(
finders
!=
null
)
{
_testTextInput
.
register
();
_commandHandlers
.
addAll
(<
String
,
CommandHandlerCallback
>{
'get_health'
:
_getHealth
,
'get_layer_tree'
:
_getLayerTree
,
'get_render_tree'
:
_getRenderTree
,
'enter_text'
:
_enterText
,
'get_text'
:
_getText
,
'request_data'
:
_requestData
,
'scroll'
:
_scroll
,
'scrollIntoView'
:
_scrollIntoView
,
'set_frame_sync'
:
_setFrameSync
,
'set_semantics'
:
_setSemantics
,
'set_text_entry_emulation'
:
_setTextEntryEmulation
,
'tap'
:
_tap
,
'waitFor'
:
_waitFor
,
'waitForAbsent'
:
_waitForAbsent
,
'waitForCondition'
:
_waitForCondition
,
'waitUntilNoTransientCallbacks'
:
_waitUntilNoTransientCallbacks
,
'waitUntilNoPendingFrame'
:
_waitUntilNoPendingFrame
,
'waitUntilFirstFrameRasterized'
:
_waitUntilFirstFrameRasterized
,
'get_semantics_id'
:
_getSemanticsId
,
'get_offset'
:
_getOffset
,
'get_diagnostics_tree'
:
_getDiagnosticsTree
,
});
_commandDeserializers
.
addAll
(<
String
,
CommandDeserializerCallback
>{
'get_health'
:
(
Map
<
String
,
String
>
params
)
=>
GetHealth
.
deserialize
(
params
),
'get_layer_tree'
:
(
Map
<
String
,
String
>
params
)
=>
GetLayerTree
.
deserialize
(
params
),
'get_render_tree'
:
(
Map
<
String
,
String
>
params
)
=>
GetRenderTree
.
deserialize
(
params
),
'enter_text'
:
(
Map
<
String
,
String
>
params
)
=>
EnterText
.
deserialize
(
params
),
'get_text'
:
(
Map
<
String
,
String
>
params
)
=>
GetText
.
deserialize
(
params
,
this
),
'request_data'
:
(
Map
<
String
,
String
>
params
)
=>
RequestData
.
deserialize
(
params
),
'scroll'
:
(
Map
<
String
,
String
>
params
)
=>
Scroll
.
deserialize
(
params
,
this
),
'scrollIntoView'
:
(
Map
<
String
,
String
>
params
)
=>
ScrollIntoView
.
deserialize
(
params
,
this
),
'set_frame_sync'
:
(
Map
<
String
,
String
>
params
)
=>
SetFrameSync
.
deserialize
(
params
),
'set_semantics'
:
(
Map
<
String
,
String
>
params
)
=>
SetSemantics
.
deserialize
(
params
),
'set_text_entry_emulation'
:
(
Map
<
String
,
String
>
params
)
=>
SetTextEntryEmulation
.
deserialize
(
params
),
'tap'
:
(
Map
<
String
,
String
>
params
)
=>
Tap
.
deserialize
(
params
,
this
),
'waitFor'
:
(
Map
<
String
,
String
>
params
)
=>
WaitFor
.
deserialize
(
params
,
this
),
'waitForAbsent'
:
(
Map
<
String
,
String
>
params
)
=>
WaitForAbsent
.
deserialize
(
params
,
this
),
'waitForCondition'
:
(
Map
<
String
,
String
>
params
)
=>
WaitForCondition
.
deserialize
(
params
),
'waitUntilNoTransientCallbacks'
:
(
Map
<
String
,
String
>
params
)
=>
WaitUntilNoTransientCallbacks
.
deserialize
(
params
),
'waitUntilNoPendingFrame'
:
(
Map
<
String
,
String
>
params
)
=>
WaitUntilNoPendingFrame
.
deserialize
(
params
),
'waitUntilFirstFrameRasterized'
:
(
Map
<
String
,
String
>
params
)
=>
WaitUntilFirstFrameRasterized
.
deserialize
(
params
),
'get_semantics_id'
:
(
Map
<
String
,
String
>
params
)
=>
GetSemanticsId
.
deserialize
(
params
,
this
),
'get_offset'
:
(
Map
<
String
,
String
>
params
)
=>
GetOffset
.
deserialize
(
params
,
this
),
'get_diagnostics_tree'
:
(
Map
<
String
,
String
>
params
)
=>
GetDiagnosticsTree
.
deserialize
(
params
,
this
),
});
registerTextInput
();
for
(
final
FinderExtension
finder
in
finders
)
{
_finderExtensions
[
finder
.
finderType
]
=
finder
;
}
for
(
final
CommandExtension
command
in
commands
)
{
_commandExtensions
[
command
.
commandKind
]
=
command
;
}
}
final
TestTextInput
_testTextInput
=
TestTextInput
(
);
final
WidgetController
_prober
=
LiveWidgetController
(
WidgetsBinding
.
instance
!
);
final
DataHandler
?
_requestDataHandler
;
final
bool
_silenceErrors
;
void
_log
(
String
message
)
{
driverLog
(
'FlutterDriverExtension'
,
message
);
}
final
WidgetController
_prober
=
LiveWidgetController
(
WidgetsBinding
.
instance
!);
final
Map
<
String
,
CommandHandlerCallback
>
_commandHandlers
=
<
String
,
CommandHandlerCallback
>{};
final
Map
<
String
,
CommandDeserializerCallback
>
_commandDeserializers
=
<
String
,
CommandDeserializerCallback
>{};
final
Map
<
String
,
FinderExtension
>
_finderExtensions
=
<
String
,
FinderExtension
>{};
/// With [_frameSync] enabled, Flutter Driver will wait to perform an action
/// until there are no pending frames in the app under test.
bool
_frameSync
=
true
;
final
Map
<
String
,
CommandExtension
>
_commandExtensions
=
<
String
,
CommandExtension
>{};
/// Processes a driver command configured by [params] and returns a result
/// as an arbitrary JSON object.
...
...
@@ -266,15 +357,10 @@ class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory
Future
<
Map
<
String
,
dynamic
>>
call
(
Map
<
String
,
String
>
params
)
async
{
final
String
commandKind
=
params
[
'command'
]!;
try
{
final
CommandHandlerCallback
commandHandler
=
_commandHandlers
[
commandKind
]!;
final
CommandDeserializerCallback
commandDeserializer
=
_commandDeserializers
[
commandKind
]!;
if
(
commandHandler
==
null
||
commandDeserializer
==
null
)
throw
'Extension
$_extensionMethod
does not support command
$commandKind
'
;
final
Command
command
=
commandDeserializer
(
params
);
final
Command
command
=
deserializeCommand
(
params
,
this
);
assert
(
WidgetsBinding
.
instance
!.
isRootWidgetAttached
||
!
command
.
requiresRootWidgetAttached
,
'No root widget is attached; have you remembered to call runApp()?'
);
Future
<
Result
?>
responseFuture
=
commandHandler
(
command
);
Future
<
Result
?>
responseFuture
=
handleCommand
(
command
,
_prober
,
this
);
if
(
command
.
timeout
!=
null
)
responseFuture
=
responseFuture
.
timeout
(
command
.
timeout
??
Duration
.
zero
);
final
Result
?
response
=
await
responseFuture
;
...
...
@@ -298,65 +384,6 @@ class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory
};
}
Future
<
Health
>
_getHealth
(
Command
command
)
async
=>
const
Health
(
HealthStatus
.
ok
);
Future
<
LayerTree
>
_getLayerTree
(
Command
command
)
async
{
return
LayerTree
(
RendererBinding
.
instance
?.
renderView
.
debugLayer
?.
toStringDeep
());
}
Future
<
RenderTree
>
_getRenderTree
(
Command
command
)
async
{
return
RenderTree
(
RendererBinding
.
instance
?.
renderView
.
toStringDeep
());
}
// This can be used to wait for the first frame being rasterized during app launch.
@Deprecated
(
'This method has been deprecated in favor of _waitForCondition. '
'This feature was deprecated after v1.9.3.'
)
Future
<
Result
?>
_waitUntilFirstFrameRasterized
(
Command
command
)
async
{
await
WidgetsBinding
.
instance
!.
waitUntilFirstFrameRasterized
;
return
null
;
}
// Waits until at the end of a frame the provided [condition] is [true].
Future
<
void
>
_waitUntilFrame
(
bool
condition
(),
[
Completer
<
void
>?
completer
])
{
completer
??=
Completer
<
void
>();
if
(!
condition
())
{
SchedulerBinding
.
instance
!.
addPostFrameCallback
((
Duration
timestamp
)
{
_waitUntilFrame
(
condition
,
completer
);
});
}
else
{
completer
.
complete
();
}
return
completer
.
future
;
}
/// Runs `finder` repeatedly until it finds one or more [Element]s.
Future
<
Finder
>
_waitForElement
(
Finder
finder
)
async
{
if
(
_frameSync
)
await
_waitUntilFrame
(()
=>
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
);
await
_waitUntilFrame
(()
=>
finder
.
evaluate
().
isNotEmpty
);
if
(
_frameSync
)
await
_waitUntilFrame
(()
=>
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
);
return
finder
;
}
/// Runs `finder` repeatedly until it finds zero [Element]s.
Future
<
Finder
>
_waitForAbsentElement
(
Finder
finder
)
async
{
if
(
_frameSync
)
await
_waitUntilFrame
(()
=>
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
);
await
_waitUntilFrame
(()
=>
finder
.
evaluate
().
isEmpty
);
if
(
_frameSync
)
await
_waitUntilFrame
(()
=>
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
);
return
finder
;
}
@override
SerializableFinder
deserializeFinder
(
Map
<
String
,
String
>
json
)
{
final
String
?
finderType
=
json
[
'finderType'
];
...
...
@@ -369,258 +396,37 @@ class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory
@override
Finder
createFinder
(
SerializableFinder
finder
)
{
if
(
_finderExtensions
.
containsKey
(
finder
.
finderType
))
{
return
_finderExtensions
[
finder
.
finderType
]!.
createFinder
(
finder
,
this
);
final
String
finderType
=
finder
.
finderType
;
if
(
_finderExtensions
.
containsKey
(
finderType
))
{
return
_finderExtensions
[
finderType
]!.
createFinder
(
finder
,
this
);
}
return
super
.
createFinder
(
finder
);
}
Future
<
TapResult
>
_tap
(
Command
command
)
async
{
final
Tap
tapCommand
=
command
as
Tap
;
final
Finder
computedFinder
=
await
_waitForElement
(
createFinder
(
tapCommand
.
finder
).
hitTestable
()
);
await
_prober
.
tap
(
computedFinder
);
return
const
TapResult
();
}
Future
<
WaitForResult
>
_waitFor
(
Command
command
)
async
{
final
WaitFor
waitForCommand
=
command
as
WaitFor
;
await
_waitForElement
(
createFinder
(
waitForCommand
.
finder
));
return
const
WaitForResult
();
}
Future
<
WaitForAbsentResult
>
_waitForAbsent
(
Command
command
)
async
{
final
WaitForAbsent
waitForAbsentCommand
=
command
as
WaitForAbsent
;
await
_waitForAbsentElement
(
createFinder
(
waitForAbsentCommand
.
finder
));
return
const
WaitForAbsentResult
();
}
Future
<
Result
?>
_waitForCondition
(
Command
command
)
async
{
assert
(
command
!=
null
);
final
WaitForCondition
waitForConditionCommand
=
command
as
WaitForCondition
;
final
WaitCondition
condition
=
deserializeCondition
(
waitForConditionCommand
.
condition
);
await
condition
.
wait
();
return
null
;
}
@Deprecated
(
'This method has been deprecated in favor of _waitForCondition. '
'This feature was deprecated after v1.9.3.'
)
Future
<
Result
?>
_waitUntilNoTransientCallbacks
(
Command
command
)
async
{
if
(
SchedulerBinding
.
instance
!.
transientCallbackCount
!=
0
)
await
_waitUntilFrame
(()
=>
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
);
return
null
;
}
/// Returns a future that waits until no pending frame is scheduled (frame is synced).
///
/// Specifically, it checks:
/// * Whether the count of transient callbacks is zero.
/// * Whether there's no pending request for scheduling a new frame.
///
/// We consider the frame is synced when both conditions are met.
///
/// This method relies on a Flutter Driver mechanism called "frame sync",
/// which waits for transient animations to finish. Persistent animations will
/// cause this to wait forever.
///
/// If a test needs to interact with the app while animations are running, it
/// should avoid this method and instead disable the frame sync using
/// `set_frame_sync` method. See [FlutterDriver.runUnsynchronized] for more
/// details on how to do this. Note, disabling frame sync will require the
/// test author to use some other method to avoid flakiness.
///
/// This method has been deprecated in favor of [_waitForCondition].
@Deprecated
(
'This method has been deprecated in favor of _waitForCondition. '
'This feature was deprecated after v1.9.3.'
)
Future
<
Result
?>
_waitUntilNoPendingFrame
(
Command
command
)
async
{
await
_waitUntilFrame
(()
{
return
SchedulerBinding
.
instance
!.
transientCallbackCount
==
0
&&
!
SchedulerBinding
.
instance
!.
hasScheduledFrame
;
});
return
null
;
}
Future
<
GetSemanticsIdResult
>
_getSemanticsId
(
Command
command
)
async
{
final
GetSemanticsId
semanticsCommand
=
command
as
GetSemanticsId
;
final
Finder
target
=
await
_waitForElement
(
createFinder
(
semanticsCommand
.
finder
));
final
Iterable
<
Element
>
elements
=
target
.
evaluate
();
if
(
elements
.
length
>
1
)
{
throw
StateError
(
'Found more than one element with the same ID:
$elements
'
);
}
final
Element
element
=
elements
.
single
;
RenderObject
?
renderObject
=
element
.
renderObject
;
SemanticsNode
?
node
;
while
(
renderObject
!=
null
&&
node
==
null
)
{
node
=
renderObject
.
debugSemantics
;
renderObject
=
renderObject
.
parent
as
RenderObject
?;
}
if
(
node
==
null
)
throw
StateError
(
'No semantics data found'
);
return
GetSemanticsIdResult
(
node
.
id
);
}
Future
<
GetOffsetResult
>
_getOffset
(
Command
command
)
async
{
final
GetOffset
getOffsetCommand
=
command
as
GetOffset
;
final
Finder
finder
=
await
_waitForElement
(
createFinder
(
getOffsetCommand
.
finder
));
final
Element
element
=
finder
.
evaluate
().
single
;
final
RenderBox
box
=
(
element
.
renderObject
as
RenderBox
?)!;
Offset
localPoint
;
switch
(
getOffsetCommand
.
offsetType
)
{
case
OffsetType
.
topLeft
:
localPoint
=
Offset
.
zero
;
break
;
case
OffsetType
.
topRight
:
localPoint
=
box
.
size
.
topRight
(
Offset
.
zero
);
break
;
case
OffsetType
.
bottomLeft
:
localPoint
=
box
.
size
.
bottomLeft
(
Offset
.
zero
);
break
;
case
OffsetType
.
bottomRight
:
localPoint
=
box
.
size
.
bottomRight
(
Offset
.
zero
);
break
;
case
OffsetType
.
center
:
localPoint
=
box
.
size
.
center
(
Offset
.
zero
);
break
;
}
final
Offset
globalPoint
=
box
.
localToGlobal
(
localPoint
);
return
GetOffsetResult
(
dx:
globalPoint
.
dx
,
dy:
globalPoint
.
dy
);
}
Future
<
DiagnosticsTreeResult
>
_getDiagnosticsTree
(
Command
command
)
async
{
final
GetDiagnosticsTree
diagnosticsCommand
=
command
as
GetDiagnosticsTree
;
final
Finder
finder
=
await
_waitForElement
(
createFinder
(
diagnosticsCommand
.
finder
));
final
Element
element
=
finder
.
evaluate
().
single
;
DiagnosticsNode
diagnosticsNode
;
switch
(
diagnosticsCommand
.
diagnosticsType
)
{
case
DiagnosticsType
.
renderObject
:
diagnosticsNode
=
element
.
renderObject
!.
toDiagnosticsNode
();
break
;
case
DiagnosticsType
.
widget
:
diagnosticsNode
=
element
.
toDiagnosticsNode
();
break
;
}
return
DiagnosticsTreeResult
(
diagnosticsNode
.
toJsonMap
(
DiagnosticsSerializationDelegate
(
subtreeDepth:
diagnosticsCommand
.
subtreeDepth
,
includeProperties:
diagnosticsCommand
.
includeProperties
,
)));
}
Future
<
ScrollResult
>
_scroll
(
Command
command
)
async
{
final
Scroll
scrollCommand
=
command
as
Scroll
;
final
Finder
target
=
await
_waitForElement
(
createFinder
(
scrollCommand
.
finder
));
final
int
totalMoves
=
scrollCommand
.
duration
.
inMicroseconds
*
scrollCommand
.
frequency
~/
Duration
.
microsecondsPerSecond
;
final
Offset
delta
=
Offset
(
scrollCommand
.
dx
,
scrollCommand
.
dy
)
/
totalMoves
.
toDouble
();
final
Duration
pause
=
scrollCommand
.
duration
~/
totalMoves
;
final
Offset
startLocation
=
_prober
.
getCenter
(
target
);
Offset
currentLocation
=
startLocation
;
final
TestPointer
pointer
=
TestPointer
(
1
);
_prober
.
binding
.
handlePointerEvent
(
pointer
.
down
(
startLocation
));
await
Future
<
void
>.
value
();
// so that down and move don't happen in the same microtask
for
(
int
moves
=
0
;
moves
<
totalMoves
;
moves
+=
1
)
{
currentLocation
=
currentLocation
+
delta
;
_prober
.
binding
.
handlePointerEvent
(
pointer
.
move
(
currentLocation
));
await
Future
<
void
>.
delayed
(
pause
);
}
_prober
.
binding
.
handlePointerEvent
(
pointer
.
up
());
return
const
ScrollResult
();
}
Future
<
ScrollResult
>
_scrollIntoView
(
Command
command
)
async
{
final
ScrollIntoView
scrollIntoViewCommand
=
command
as
ScrollIntoView
;
final
Finder
target
=
await
_waitForElement
(
createFinder
(
scrollIntoViewCommand
.
finder
));
await
Scrollable
.
ensureVisible
(
target
.
evaluate
().
single
,
duration:
const
Duration
(
milliseconds:
100
),
alignment:
scrollIntoViewCommand
.
alignment
);
return
const
ScrollResult
();
}
Future
<
GetTextResult
>
_getText
(
Command
command
)
async
{
final
GetText
getTextCommand
=
command
as
GetText
;
final
Finder
target
=
await
_waitForElement
(
createFinder
(
getTextCommand
.
finder
));
final
Widget
widget
=
target
.
evaluate
().
single
.
widget
;
String
?
text
;
if
(
widget
.
runtimeType
==
Text
)
{
text
=
(
widget
as
Text
).
data
;
}
else
if
(
widget
.
runtimeType
==
RichText
)
{
final
RichText
richText
=
widget
as
RichText
;
if
(
richText
.
text
.
runtimeType
==
TextSpan
)
{
text
=
(
richText
.
text
as
TextSpan
).
text
;
}
}
else
if
(
widget
.
runtimeType
==
TextField
)
{
text
=
(
widget
as
TextField
).
controller
?.
text
;
}
else
if
(
widget
.
runtimeType
==
TextFormField
)
{
text
=
(
widget
as
TextFormField
).
controller
?.
text
;
}
else
if
(
widget
.
runtimeType
==
EditableText
)
{
text
=
(
widget
as
EditableText
).
controller
.
text
;
}
if
(
text
==
null
)
{
throw
UnsupportedError
(
'Type
${widget.runtimeType.toString()}
is currently not supported by getText'
);
}
return
GetTextResult
(
text
);
}
Future
<
SetTextEntryEmulationResult
>
_setTextEntryEmulation
(
Command
command
)
async
{
final
SetTextEntryEmulation
setTextEntryEmulationCommand
=
command
as
SetTextEntryEmulation
;
if
(
setTextEntryEmulationCommand
.
enabled
)
{
_testTextInput
.
register
();
}
else
{
_testTextInput
.
unregister
();
}
return
const
SetTextEntryEmulationResult
();
@override
Command
deserializeCommand
(
Map
<
String
,
String
>
params
,
DeserializeFinderFactory
finderFactory
)
{
final
String
?
kind
=
params
[
'command'
];
if
(
_commandExtensions
.
containsKey
(
kind
))
{
return
_commandExtensions
[
kind
]!.
deserialize
(
params
,
finderFactory
,
this
);
}
Future
<
EnterTextResult
>
_enterText
(
Command
command
)
async
{
if
(!
_testTextInput
.
isRegistered
)
{
throw
'Unable to fulfill `FlutterDriver.enterText`. Text emulation is '
'disabled. You can enable it using `FlutterDriver.setTextEntryEmulation`.'
;
}
final
EnterText
enterTextCommand
=
command
as
EnterText
;
_testTextInput
.
enterText
(
enterTextCommand
.
text
);
return
const
EnterTextResult
();
return
super
.
deserializeCommand
(
params
,
finderFactory
);
}
Future
<
RequestDataResult
>
_requestData
(
Command
command
)
async
{
final
RequestData
requestDataCommand
=
command
as
RequestData
;
return
RequestDataResult
(
_requestDataHandler
==
null
?
'No requestData Extension registered'
:
await
_requestDataHandler
!(
requestDataCommand
.
message
));
@override
@protected
DataHandler
?
getDataHandler
()
{
return
_requestDataHandler
;
}
Future
<
SetFrameSyncResult
>
_setFrameSync
(
Command
command
)
async
{
final
SetFrameSync
setFrameSyncCommand
=
command
as
SetFrameSync
;
_frameSync
=
setFrameSyncCommand
.
enabled
;
return
const
SetFrameSyncResult
();
@override
Future
<
Result
?>
handleCommand
(
Command
command
,
WidgetController
prober
,
CreateFinderFactory
finderFactory
)
{
final
String
kind
=
command
.
kind
;
if
(
_commandExtensions
.
containsKey
(
kind
))
{
return
_commandExtensions
[
kind
]!.
call
(
command
,
prober
,
finderFactory
,
this
);
}
SemanticsHandle
?
_semantics
;
bool
get
_semanticsIsEnabled
=>
RendererBinding
.
instance
!.
pipelineOwner
.
semanticsOwner
!=
null
;
Future
<
SetSemanticsResult
>
_setSemantics
(
Command
command
)
async
{
final
SetSemantics
setSemanticsCommand
=
command
as
SetSemantics
;
final
bool
semanticsWasEnabled
=
_semanticsIsEnabled
;
if
(
setSemanticsCommand
.
enabled
&&
_semantics
==
null
)
{
_semantics
=
RendererBinding
.
instance
!.
pipelineOwner
.
ensureSemantics
();
if
(!
semanticsWasEnabled
)
{
// wait for the first frame where semantics is enabled.
final
Completer
<
void
>
completer
=
Completer
<
void
>();
SchedulerBinding
.
instance
!.
addPostFrameCallback
((
Duration
d
)
{
completer
.
complete
();
});
await
completer
.
future
;
}
}
else
if
(!
setSemanticsCommand
.
enabled
&&
_semantics
!=
null
)
{
_semantics
!.
dispose
();
_semantics
=
null
;
}
return
SetSemanticsResult
(
semanticsWasEnabled
!=
_semanticsIsEnabled
);
return
super
.
handleCommand
(
command
,
prober
,
finderFactory
);
}
}
packages/flutter_driver/test/src/real_tests/extension_test.dart
View file @
d2d07219
...
...
@@ -20,6 +20,8 @@ import 'package:flutter_driver/src/common/wait.dart';
import
'package:flutter_driver/src/extension/extension.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'stubs/stub_command.dart'
;
import
'stubs/stub_command_extension.dart'
;
import
'stubs/stub_finder.dart'
;
import
'stubs/stub_finder_extension.dart'
;
...
...
@@ -984,6 +986,100 @@ void main() {
});
});
group
(
'extension commands'
,
()
{
int
invokes
=
0
;
final
VoidCallback
stubCallback
=
()
=>
invokes
++;
final
Widget
debugTree
=
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
Column
(
children:
<
Widget
>[
FlatButton
(
child:
const
Text
(
'Whatever'
),
key:
const
ValueKey
<
String
>(
'Button'
),
onPressed:
stubCallback
,
),
],
),
),
);
setUp
(()
{
invokes
=
0
;
});
testWidgets
(
'unknown extension command'
,
(
WidgetTester
tester
)
async
{
final
FlutterDriverExtension
driverExtension
=
FlutterDriverExtension
(
(
String
arg
)
async
=>
''
,
true
,
commands:
<
CommandExtension
>[],
);
Future
<
Map
<
String
,
dynamic
>>
invokeCommand
(
SerializableFinder
finder
,
int
times
)
async
{
final
Map
<
String
,
String
>
arguments
=
StubNestedCommand
(
finder
,
times
).
serialize
();
return
await
driverExtension
.
call
(
arguments
);
}
await
tester
.
pumpWidget
(
debugTree
);
final
Map
<
String
,
dynamic
>
result
=
await
invokeCommand
(
ByValueKey
(
'Button'
),
10
);
expect
(
result
[
'isError'
],
true
);
expect
(
result
[
'response'
]
is
String
,
true
);
expect
(
result
[
'response'
]
as
String
,
contains
(
'Unsupported command kind StubNestedCommand'
));
});
testWidgets
(
'nested command'
,
(
WidgetTester
tester
)
async
{
final
FlutterDriverExtension
driverExtension
=
FlutterDriverExtension
(
(
String
arg
)
async
=>
''
,
true
,
commands:
<
CommandExtension
>[
StubNestedCommandExtension
(),
],
);
Future
<
StubCommandResult
>
invokeCommand
(
SerializableFinder
finder
,
int
times
)
async
{
await
driverExtension
.
call
(
const
SetFrameSync
(
false
).
serialize
());
// disable frame sync for test to avoid lock
final
Map
<
String
,
String
>
arguments
=
StubNestedCommand
(
finder
,
times
,
timeout:
const
Duration
(
seconds:
1
)).
serialize
();
final
Map
<
String
,
dynamic
>
response
=
await
driverExtension
.
call
(
arguments
);
final
Map
<
String
,
dynamic
>
commandResponse
=
response
[
'response'
]
as
Map
<
String
,
dynamic
>;
return
StubCommandResult
(
commandResponse
[
'resultParam'
]
as
String
);
}
await
tester
.
pumpWidget
(
debugTree
);
const
int
times
=
10
;
final
StubCommandResult
result
=
await
invokeCommand
(
ByValueKey
(
'Button'
),
times
);
expect
(
result
.
resultParam
,
'stub response'
);
expect
(
invokes
,
times
);
});
testWidgets
(
'prober command'
,
(
WidgetTester
tester
)
async
{
final
FlutterDriverExtension
driverExtension
=
FlutterDriverExtension
(
(
String
arg
)
async
=>
''
,
true
,
commands:
<
CommandExtension
>[
StubProberCommandExtension
(),
],
);
Future
<
StubCommandResult
>
invokeCommand
(
SerializableFinder
finder
,
int
times
)
async
{
await
driverExtension
.
call
(
const
SetFrameSync
(
false
).
serialize
());
// disable frame sync for test to avoid lock
final
Map
<
String
,
String
>
arguments
=
StubProberCommand
(
finder
,
times
,
timeout:
const
Duration
(
seconds:
1
)).
serialize
();
final
Map
<
String
,
dynamic
>
response
=
await
driverExtension
.
call
(
arguments
);
final
Map
<
String
,
dynamic
>
commandResponse
=
response
[
'response'
]
as
Map
<
String
,
dynamic
>;
return
StubCommandResult
(
commandResponse
[
'resultParam'
]
as
String
);
}
await
tester
.
pumpWidget
(
debugTree
);
const
int
times
=
10
;
final
StubCommandResult
result
=
await
invokeCommand
(
ByValueKey
(
'Button'
),
times
);
expect
(
result
.
resultParam
,
'stub response'
);
expect
(
invokes
,
times
);
});
});
group
(
'waitUntilFrameSync'
,
()
{
FlutterDriverExtension
driverExtension
;
Map
<
String
,
dynamic
>
result
;
...
...
packages/flutter_driver/test/src/real_tests/find_test.dart
View file @
d2d07219
...
...
@@ -4,6 +4,7 @@
// @dart = 2.8
import
'package:flutter_driver/driver_extension.dart'
;
import
'package:flutter_driver/flutter_driver.dart'
;
import
'package:flutter_driver/src/common/find.dart'
;
import
'package:mockito/mockito.dart'
;
...
...
packages/flutter_driver/test/src/real_tests/stubs/stub_command.dart
0 → 100644
View file @
d2d07219
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter_driver/driver_extension.dart'
;
import
'package:flutter_driver/flutter_driver.dart'
;
class
StubNestedCommand
extends
CommandWithTarget
{
StubNestedCommand
(
SerializableFinder
finder
,
this
.
times
,
{
Duration
?
timeout
})
:
super
(
finder
,
timeout:
timeout
);
StubNestedCommand
.
deserialize
(
Map
<
String
,
String
>
json
,
DeserializeFinderFactory
finderFactory
)
:
times
=
int
.
parse
(
json
[
'times'
]!),
super
.
deserialize
(
json
,
finderFactory
);
@override
Map
<
String
,
String
>
serialize
()
{
return
super
.
serialize
()..
addAll
(<
String
,
String
>{
'times'
:
'
$times
'
});
}
@override
String
get
kind
=>
'StubNestedCommand'
;
final
int
times
;
}
class
StubProberCommand
extends
CommandWithTarget
{
StubProberCommand
(
SerializableFinder
finder
,
this
.
times
,
{
Duration
?
timeout
})
:
super
(
finder
,
timeout:
timeout
);
StubProberCommand
.
deserialize
(
Map
<
String
,
String
>
json
,
DeserializeFinderFactory
finderFactory
)
:
times
=
int
.
parse
(
json
[
'times'
]!),
super
.
deserialize
(
json
,
finderFactory
);
@override
Map
<
String
,
String
>
serialize
()
{
return
super
.
serialize
()..
addAll
(<
String
,
String
>{
'times'
:
'
$times
'
});
}
@override
String
get
kind
=>
'StubProberCommand'
;
final
int
times
;
}
class
StubCommandResult
extends
Result
{
const
StubCommandResult
(
this
.
resultParam
);
final
String
resultParam
;
@override
Map
<
String
,
dynamic
>
toJson
()
{
return
<
String
,
dynamic
>{
'resultParam'
:
resultParam
,
};
}
}
packages/flutter_driver/test/src/real_tests/stubs/stub_command_extension.dart
0 → 100644
View file @
d2d07219
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter_driver/driver_extension.dart'
;
import
'package:flutter_driver/flutter_driver.dart'
;
import
'package:flutter_driver/src/common/message.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'stub_command.dart'
;
class
StubNestedCommandExtension
extends
CommandExtension
{
@override
String
get
commandKind
=>
'StubNestedCommand'
;
@override
Future
<
Result
>
call
(
Command
command
,
WidgetController
prober
,
CreateFinderFactory
finderFactory
,
CommandHandlerFactory
handlerFactory
)
async
{
final
StubNestedCommand
stubCommand
=
command
as
StubNestedCommand
;
handlerFactory
.
waitForElement
(
finderFactory
.
createFinder
(
stubCommand
.
finder
));
for
(
int
index
=
0
;
index
<
stubCommand
.
times
;
index
++)
{
await
handlerFactory
.
handleCommand
(
Tap
(
stubCommand
.
finder
),
prober
,
finderFactory
);
}
return
const
StubCommandResult
(
'stub response'
);
}
@override
Command
deserialize
(
Map
<
String
,
String
>
params
,
DeserializeFinderFactory
finderFactory
,
DeserializeCommandFactory
commandFactory
)
{
return
StubNestedCommand
.
deserialize
(
params
,
finderFactory
);
}
}
class
StubProberCommandExtension
extends
CommandExtension
{
@override
String
get
commandKind
=>
'StubProberCommand'
;
@override
Future
<
Result
>
call
(
Command
command
,
WidgetController
prober
,
CreateFinderFactory
finderFactory
,
CommandHandlerFactory
handlerFactory
)
async
{
final
StubProberCommand
stubCommand
=
command
as
StubProberCommand
;
final
Finder
finder
=
finderFactory
.
createFinder
(
stubCommand
.
finder
);
handlerFactory
.
waitForElement
(
finder
);
for
(
int
index
=
0
;
index
<
stubCommand
.
times
;
index
++)
{
await
prober
.
tap
(
finder
);
}
return
const
StubCommandResult
(
'stub response'
);
}
@override
Command
deserialize
(
Map
<
String
,
String
>
params
,
DeserializeFinderFactory
finderFactory
,
DeserializeCommandFactory
commandFactory
)
{
return
StubProberCommand
.
deserialize
(
params
,
finderFactory
);
}
}
packages/flutter_driver/test/src/real_tests/stubs/stub_finder_extension.dart
View file @
d2d07219
...
...
@@ -3,9 +3,9 @@
// found in the LICENSE file.
import
'package:flutter/src/widgets/framework.dart'
;
import
'package:flutter_driver/driver_extension.dart'
;
import
'package:flutter_driver/src/common/create_finder_factory.dart'
;
import
'package:flutter_test/src/finders.dart'
;
import
'package:flutter_driver/driver_extension.dart'
;
import
'package:flutter_driver/src/common/handler_factory.dart'
;
import
'package:flutter_driver/src/common/find.dart'
;
import
'stub_finder.dart'
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment