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
5e27ebbe
Unverified
Commit
5e27ebbe
authored
Mar 19, 2019
by
Dan Field
Committed by
GitHub
Mar 19, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add semantic label finders (#29342)
* Add semantic label finders
parent
79e3bf4a
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
206 additions
and
0 deletions
+206
-0
find.dart
packages/flutter_driver/lib/src/common/find.dart
+39
-0
driver.dart
packages/flutter_driver/lib/src/driver/driver.dart
+3
-0
extension.dart
packages/flutter_driver/lib/src/extension/extension.dart
+17
-0
flutter_driver_test.dart
packages/flutter_driver/test/flutter_driver_test.dart
+29
-0
finders.dart
packages/flutter_test/lib/src/finders.dart
+45
-0
finders_test.dart
packages/flutter_test/test/finders_test.dart
+73
-0
No files found.
packages/flutter_driver/lib/src/common/find.dart
View file @
5e27ebbe
...
@@ -135,6 +135,7 @@ abstract class SerializableFinder {
...
@@ -135,6 +135,7 @@ abstract class SerializableFinder {
case
'ByType'
:
return
ByType
.
deserialize
(
json
);
case
'ByType'
:
return
ByType
.
deserialize
(
json
);
case
'ByValueKey'
:
return
ByValueKey
.
deserialize
(
json
);
case
'ByValueKey'
:
return
ByValueKey
.
deserialize
(
json
);
case
'ByTooltipMessage'
:
return
ByTooltipMessage
.
deserialize
(
json
);
case
'ByTooltipMessage'
:
return
ByTooltipMessage
.
deserialize
(
json
);
case
'BySemanticsLabel'
:
return
BySemanticsLabel
.
deserialize
(
json
);
case
'ByText'
:
return
ByText
.
deserialize
(
json
);
case
'ByText'
:
return
ByText
.
deserialize
(
json
);
case
'PageBack'
:
return
PageBack
();
case
'PageBack'
:
return
PageBack
();
}
}
...
@@ -164,6 +165,44 @@ class ByTooltipMessage extends SerializableFinder {
...
@@ -164,6 +165,44 @@ class ByTooltipMessage extends SerializableFinder {
}
}
}
}
/// A Flutter Driver finder that finds widgets by semantic label.
///
/// If the [label] property is a [String], the finder will try to find an exact
/// match. If it is a [RegExp], it will return true for [RegExp.hasMatch].
class
BySemanticsLabel
extends
SerializableFinder
{
/// Creates a semantic label finder given the [label].
BySemanticsLabel
(
this
.
label
);
/// A [Pattern] matching the [Semantics.properties.label].
///
/// If this is a [String], it will be treated as an exact match.
final
Pattern
label
;
@override
final
String
finderType
=
'BySemanticsLabel'
;
@override
Map
<
String
,
String
>
serialize
()
{
if
(
label
is
RegExp
)
{
final
RegExp
regExp
=
label
;
return
super
.
serialize
()..
addAll
(<
String
,
String
>{
'label'
:
regExp
.
pattern
,
'isRegExp'
:
'true'
,
});
}
else
{
return
super
.
serialize
()..
addAll
(<
String
,
String
>{
'label'
:
label
,
});
}
}
/// Deserializes the finder from JSON generated by [serialize].
static
BySemanticsLabel
deserialize
(
Map
<
String
,
String
>
json
)
{
final
bool
isRegExp
=
json
[
'isRegExp'
]
==
'true'
;
return
BySemanticsLabel
(
isRegExp
?
RegExp
(
json
[
'label'
])
:
json
[
'label'
]);
}
}
/// A Flutter Driver finder that finds widgets by [text] inside a [Text] or
/// A Flutter Driver finder that finds widgets by [text] inside a [Text] or
/// [EditableText] widget.
/// [EditableText] widget.
class
ByText
extends
SerializableFinder
{
class
ByText
extends
SerializableFinder
{
...
...
packages/flutter_driver/lib/src/driver/driver.dart
View file @
5e27ebbe
...
@@ -939,6 +939,9 @@ class CommonFinders {
...
@@ -939,6 +939,9 @@ class CommonFinders {
/// Finds widgets with a tooltip with the given [message].
/// Finds widgets with a tooltip with the given [message].
SerializableFinder
byTooltip
(
String
message
)
=>
ByTooltipMessage
(
message
);
SerializableFinder
byTooltip
(
String
message
)
=>
ByTooltipMessage
(
message
);
/// Finds widgets with the given semantics [label].
SerializableFinder
bySemanticsLabel
(
Pattern
label
)
=>
BySemanticsLabel
(
label
);
/// Finds widgets whose class name matches the given string.
/// Finds widgets whose class name matches the given string.
SerializableFinder
byType
(
String
type
)
=>
ByType
(
type
);
SerializableFinder
byType
(
String
type
)
=>
ByType
(
type
);
...
...
packages/flutter_driver/lib/src/extension/extension.dart
View file @
5e27ebbe
...
@@ -135,6 +135,7 @@ class FlutterDriverExtension {
...
@@ -135,6 +135,7 @@ class FlutterDriverExtension {
_finders
.
addAll
(<
String
,
FinderConstructor
>{
_finders
.
addAll
(<
String
,
FinderConstructor
>{
'ByText'
:
(
SerializableFinder
finder
)
=>
_createByTextFinder
(
finder
),
'ByText'
:
(
SerializableFinder
finder
)
=>
_createByTextFinder
(
finder
),
'ByTooltipMessage'
:
(
SerializableFinder
finder
)
=>
_createByTooltipMessageFinder
(
finder
),
'ByTooltipMessage'
:
(
SerializableFinder
finder
)
=>
_createByTooltipMessageFinder
(
finder
),
'BySemanticsLabel'
:
(
SerializableFinder
finder
)
=>
_createBySemanticsLabelFinder
(
finder
),
'ByValueKey'
:
(
SerializableFinder
finder
)
=>
_createByValueKeyFinder
(
finder
),
'ByValueKey'
:
(
SerializableFinder
finder
)
=>
_createByValueKeyFinder
(
finder
),
'ByType'
:
(
SerializableFinder
finder
)
=>
_createByTypeFinder
(
finder
),
'ByType'
:
(
SerializableFinder
finder
)
=>
_createByTypeFinder
(
finder
),
'PageBack'
:
(
SerializableFinder
finder
)
=>
_createPageBackFinder
(),
'PageBack'
:
(
SerializableFinder
finder
)
=>
_createPageBackFinder
(),
...
@@ -262,6 +263,22 @@ class FlutterDriverExtension {
...
@@ -262,6 +263,22 @@ class FlutterDriverExtension {
},
description:
'widget with text tooltip "
${arguments.text}
"'
);
},
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
)
{
Finder
_createByValueKeyFinder
(
ByValueKey
arguments
)
{
switch
(
arguments
.
keyValueType
)
{
switch
(
arguments
.
keyValueType
)
{
case
'int'
:
case
'int'
:
...
...
packages/flutter_driver/test/flutter_driver_test.dart
View file @
5e27ebbe
...
@@ -158,6 +158,35 @@ void main() {
...
@@ -158,6 +158,35 @@ void main() {
});
});
});
});
group
(
'BySemanticsLabel'
,
()
{
test
(
'finds by Semantic label using String'
,
()
async
{
when
(
mockIsolate
.
invokeExtension
(
any
,
any
)).
thenAnswer
((
Invocation
i
)
{
expect
(
i
.
positionalArguments
[
1
],
<
String
,
String
>{
'command'
:
'tap'
,
'timeout'
:
_kSerializedTestTimeout
,
'finderType'
:
'BySemanticsLabel'
,
'label'
:
'foo'
,
});
return
makeMockResponse
(<
String
,
dynamic
>{});
});
await
driver
.
tap
(
find
.
bySemanticsLabel
(
'foo'
),
timeout:
_kTestTimeout
);
});
test
(
'finds by Semantic label using RegExp'
,
()
async
{
when
(
mockIsolate
.
invokeExtension
(
any
,
any
)).
thenAnswer
((
Invocation
i
)
{
expect
(
i
.
positionalArguments
[
1
],
<
String
,
String
>{
'command'
:
'tap'
,
'timeout'
:
_kSerializedTestTimeout
,
'finderType'
:
'BySemanticsLabel'
,
'label'
:
'^foo'
,
'isRegExp'
:
'true'
,
});
return
makeMockResponse
(<
String
,
dynamic
>{});
});
await
driver
.
tap
(
find
.
bySemanticsLabel
(
RegExp
(
'^foo'
)),
timeout:
_kTestTimeout
);
});
});
group
(
'tap'
,
()
{
group
(
'tap'
,
()
{
test
(
'requires a target reference'
,
()
async
{
test
(
'requires a target reference'
,
()
async
{
expect
(
driver
.
tap
(
null
),
throwsA
(
isInstanceOf
<
DriverError
>()));
expect
(
driver
.
tap
(
null
),
throwsA
(
isInstanceOf
<
DriverError
>()));
...
...
packages/flutter_test/lib/src/finders.dart
View file @
5e27ebbe
...
@@ -272,6 +272,51 @@ class CommonFinders {
...
@@ -272,6 +272,51 @@ class CommonFinders {
Finder
ancestor
({
Finder
of
,
Finder
matching
,
bool
matchRoot
=
false
})
{
Finder
ancestor
({
Finder
of
,
Finder
matching
,
bool
matchRoot
=
false
})
{
return
_AncestorFinder
(
of
,
matching
,
matchRoot:
matchRoot
);
return
_AncestorFinder
(
of
,
matching
,
matchRoot:
matchRoot
);
}
}
/// Finds [Semantics] widgets matching the given `label`, either by
/// [RegExp.hasMatch] or string equality.
///
/// The framework may combine semantics labels in certain scenarios, such as
/// when multiple [Text] widgets are in a [MaterialButton] widget. In such a
/// case, it may be preferable to match by regular expression. Consumers of
/// this API __must not__ introduce unsuitable content into the semantics tree
/// for the purposes of testing; in particular, you should prefer matching by
/// regular expression rather than by string if the framework has combined
/// your semantics, and not try to force the framework to break up the
/// semantics nodes. Breaking up the nodes would have an undesirable effect on
/// screen readers and other accessibility services.
///
/// ## Sample code
///
/// ```dart
/// expect(find.BySemanticsLabel('Back'), findsOneWidget);
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
Finder
bySemanticsLabel
(
Pattern
label
,
{
bool
skipOffstage
=
true
})
{
if
(
WidgetsBinding
.
instance
.
pipelineOwner
.
semanticsOwner
==
null
)
throw
StateError
(
'Semantics are not enabled. '
'Make sure to call tester.enableSemantics() before using '
'this finder, and call dispose on its return value after.'
);
return
byElementPredicate
(
(
Element
element
)
{
// Multiple elements can have the same renderObject - we want the "owner"
// of the renderObject, i.e. the RenderObjectElement.
if
(
element
is
!
RenderObjectElement
)
{
return
false
;
}
final
String
semanticsLabel
=
element
.
renderObject
?.
debugSemantics
?.
label
;
if
(
semanticsLabel
==
null
)
{
return
false
;
}
return
label
is
RegExp
?
label
.
hasMatch
(
semanticsLabel
)
:
label
==
semanticsLabel
;
},
skipOffstage:
skipOffstage
,
);
}
}
}
/// Searches a widget tree and returns nodes that match a particular
/// Searches a widget tree and returns nodes that match a particular
...
...
packages/flutter_test/test/finders_test.dart
View file @
5e27ebbe
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'package:flutter/rendering.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
...
@@ -29,6 +30,53 @@ void main() {
...
@@ -29,6 +30,53 @@ void main() {
});
});
});
});
group
(
'semantics'
,
()
{
testWidgets
(
'Throws StateError if semantics are not enabled'
,
(
WidgetTester
tester
)
async
{
expect
(()
=>
find
.
bySemanticsLabel
(
'Add'
),
throwsStateError
);
});
testWidgets
(
'finds Semantically labeled widgets'
,
(
WidgetTester
tester
)
async
{
final
SemanticsHandle
semanticsHandle
=
tester
.
ensureSemantics
();
await
tester
.
pumpWidget
(
_boilerplate
(
Semantics
(
label:
'Add'
,
button:
true
,
child:
const
FlatButton
(
child:
Text
(
'+'
),
onPressed:
null
,
),
),
));
expect
(
find
.
bySemanticsLabel
(
'Add'
),
findsOneWidget
);
semanticsHandle
.
dispose
();
});
testWidgets
(
'finds Semantically labeled widgets by RegExp'
,
(
WidgetTester
tester
)
async
{
final
SemanticsHandle
semanticsHandle
=
tester
.
ensureSemantics
();
await
tester
.
pumpWidget
(
_boilerplate
(
Semantics
(
container:
true
,
child:
Row
(
children:
const
<
Widget
>[
Text
(
'Hello'
),
Text
(
'World'
),
]),
),
));
expect
(
find
.
bySemanticsLabel
(
'Hello'
),
findsNothing
);
expect
(
find
.
bySemanticsLabel
(
RegExp
(
r'^Hello'
)),
findsOneWidget
);
semanticsHandle
.
dispose
();
});
testWidgets
(
'finds Semantically labeled widgets without explicit Semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsHandle
semanticsHandle
=
tester
.
ensureSemantics
();
await
tester
.
pumpWidget
(
_boilerplate
(
const
SimpleCustomSemanticsWidget
(
'Foo'
)
));
expect
(
find
.
bySemanticsLabel
(
'Foo'
),
findsOneWidget
);
semanticsHandle
.
dispose
();
});
});
group
(
'hitTestable'
,
()
{
group
(
'hitTestable'
,
()
{
testWidgets
(
'excludes non-hit-testable widgets'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'excludes non-hit-testable widgets'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
await
tester
.
pumpWidget
(
...
@@ -92,3 +140,28 @@ Widget _boilerplate(Widget child) {
...
@@ -92,3 +140,28 @@ Widget _boilerplate(Widget child) {
child:
child
,
child:
child
,
);
);
}
}
class
SimpleCustomSemanticsWidget
extends
LeafRenderObjectWidget
{
const
SimpleCustomSemanticsWidget
(
this
.
label
);
final
String
label
;
@override
RenderObject
createRenderObject
(
BuildContext
context
)
=>
SimpleCustomSemanticsRenderObject
(
label
);
}
class
SimpleCustomSemanticsRenderObject
extends
RenderBox
{
SimpleCustomSemanticsRenderObject
(
this
.
label
);
final
String
label
;
@override
bool
get
sizedByParent
=>
true
;
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
config
..
label
=
label
..
textDirection
=
TextDirection
.
ltr
;
}
}
\ No newline at end of file
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