Skip to content

Commit 9b2cfbc

Browse files
saikrishna321sudharsan-selvarajSrinivasanTarget
authored
add support for decendant and ancestor locator (#36)
* add support for decendant and ancestor locator * Bump server version Co-authored-by: SrinivasanTarget <[email protected]> --------- Co-authored-by: sudharsan-selvaraj <[email protected]> Co-authored-by: SrinivasanTarget <[email protected]>
1 parent 773b0c4 commit 9b2cfbc

File tree

8 files changed

+250
-65
lines changed

8 files changed

+250
-65
lines changed

demo-app/pubspec.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ packages:
1515
path: "../server"
1616
relative: true
1717
source: path
18-
version: "0.0.23"
18+
version: "0.0.29"
1919
archive:
2020
dependency: transitive
2121
description:
@@ -84,10 +84,10 @@ packages:
8484
dependency: "direct main"
8585
description:
8686
name: carousel_slider
87-
sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42"
87+
sha256: bcc61735345c9ab5cb81073896579e735f81e35fd588907a393143ea986be8ff
8888
url: "https://pub.dev"
8989
source: hosted
90-
version: "4.2.1"
90+
version: "5.1.1"
9191
characters:
9292
dependency: transitive
9393
description:

server/lib/src/handler/find_elements.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import 'package:appium_flutter_server/src/utils/element_helper.dart';
1111
import 'package:flutter_test/flutter_test.dart';
1212
import 'package:shelf_plus/shelf_plus.dart';
1313

14-
class FindElementstHandler extends RequestHandler {
15-
FindElementstHandler(super.route);
14+
class FindElementsHandler extends RequestHandler {
15+
FindElementsHandler(super.route);
1616

1717
@override
1818
Future<AppiumResponse> handle(Request request) async {
Lines changed: 162 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:appium_flutter_server/src/internal/widget_predicates.dart';
2+
import 'package:appium_flutter_server/src/models/api/find_element.dart';
23
import 'package:flutter/material.dart';
34
import 'package:flutter_test/flutter_test.dart';
45

@@ -10,12 +11,12 @@ enum ElementLookupStrategy {
1011
BY_TEXT_CONTAINING,
1112
BY_TYPE,
1213
BY_ICON_POINT,
13-
BY_ICON_NAME
14-
// BY_ICON,
15-
// BY_ELEMENT_PREDICATE,
16-
// BY_SUBTYPE,
17-
// BY_WIDGET,
18-
//BY_WIDGET_PREDICATE,
14+
BY_ICON_NAME,
15+
// Complex strategies
16+
BY_ANCESTOR,
17+
BY_DESCENDANT,
18+
BY_PAGE_OBJECT,
19+
BY_CUSTOM_PREDICATE,
1920
}
2021

2122
var a = Icons.accessible;
@@ -25,37 +26,162 @@ extension ElementLookupStrategyExtension on ElementLookupStrategy {
2526
return ElementLookupStrategy.values.byName(strategyName);
2627
}
2728

28-
Finder toFinder(dynamic selector) {
29+
/// Convert FindElementModel to Finder - supports both simple and complex finders
30+
Future<Finder> toFinder(FindElementModel model) async {
2931
switch (this) {
32+
// Simple strategies
3033
case ElementLookupStrategy.BY_KEY:
31-
return find.byKey(Key(selector), skipOffstage: false);
34+
return find.byKey(Key(model.selectorString!), skipOffstage: false);
3235
case ElementLookupStrategy.BY_SEMANTICS_LABEL:
33-
return find.bySemanticsLabel(selector, skipOffstage: false);
36+
return find.bySemanticsLabel(model.selectorString!,
37+
skipOffstage: false);
3438
case ElementLookupStrategy.BY_TOOLTIP:
35-
return find.byTooltip(selector, skipOffstage: false);
39+
return find.byTooltip(model.selectorString!, skipOffstage: false);
3640
case ElementLookupStrategy.BY_TEXT:
37-
return find.text(selector, skipOffstage: false);
41+
return find.text(model.selectorString!, skipOffstage: false);
3842
case ElementLookupStrategy.BY_TEXT_CONTAINING:
39-
return find.textContaining(selector, skipOffstage: false);
43+
return find.textContaining(model.selectorString!, skipOffstage: false);
4044
case ElementLookupStrategy.BY_TYPE:
41-
return find.byWidgetPredicate(filterByWidgetName(selector),
42-
skipOffstage: false);
45+
return find.byWidgetPredicate(
46+
filterByWidgetName(model.selectorString!),
47+
skipOffstage: false,
48+
);
4349
case ElementLookupStrategy.BY_ICON_POINT:
44-
return find.byWidgetPredicate(filterByIconCode(int.parse(selector)),
45-
skipOffstage: false);
46-
// return find.byIcon(const IconData(0xe0c4));
47-
// case ElementLookupStrategy.BY_ELEMENT_PREDICATE:
48-
// return find.byElementPredicate(Key(selector));
49-
// case ElementLookupStrategy.BY_SUBTYPE:
50-
// return find.bySubtype<SnackBar>();
51-
// case ElementLookupStrategy.BY_TYPE:
52-
// return find.byType(SingleChildRenderObjectElement);
53-
// case ElementLookupStrategy.BY_WIDGET:
54-
// return find.byWidget(SnackBar);
55-
// case ElementLookupStrategy.BY_WIDGET_PREDICATE:
56-
// return find.byWidget(SnackBar);
50+
return find.byWidgetPredicate(
51+
filterByIconCode(int.parse(model.selectorString!)),
52+
skipOffstage: false,
53+
);
54+
case ElementLookupStrategy.BY_ICON_NAME:
55+
return find.byIcon(_getIconByName(model.selectorString!));
56+
57+
// Complex strategies
58+
case ElementLookupStrategy.BY_ANCESTOR:
59+
return await _createAncestorFinder(model);
60+
case ElementLookupStrategy.BY_DESCENDANT:
61+
return await _createDescendantFinder(model);
62+
case ElementLookupStrategy.BY_PAGE_OBJECT:
63+
return await _createPageObjectFinder(model);
64+
case ElementLookupStrategy.BY_CUSTOM_PREDICATE:
65+
return _createCustomPredicateFinder(model);
66+
}
67+
}
68+
69+
/// Create ancestor finder from complex model
70+
Future<Finder> _createAncestorFinder(FindElementModel model) async {
71+
final selectorMap = model.selectorMap!;
72+
final ofModel = FindElementModel.fromJson(selectorMap['of']);
73+
final matchingModel = FindElementModel.fromJson(selectorMap['matching']);
74+
final matchRoot = selectorMap['matchRoot'] ?? false;
75+
76+
final ofFinder = await _resolveFinder(ofModel);
77+
final matchingFinder = await _resolveFinder(matchingModel);
78+
79+
return find.ancestor(
80+
of: ofFinder,
81+
matching: matchingFinder,
82+
matchRoot: matchRoot,
83+
);
84+
}
85+
86+
/// Create descendant finder from complex model
87+
Future<Finder> _createDescendantFinder(FindElementModel model) async {
88+
final selectorMap = model.selectorMap!;
89+
final ofModel = FindElementModel.fromJson(selectorMap['of']);
90+
final matchingModel = FindElementModel.fromJson(selectorMap['matching']);
91+
final matchRoot = selectorMap['matchRoot'] ?? false;
92+
93+
final ofFinder = await _resolveFinder(ofModel);
94+
final matchingFinder = await _resolveFinder(matchingModel);
95+
96+
return find.descendant(
97+
of: ofFinder,
98+
matching: matchingFinder,
99+
matchRoot: matchRoot,
100+
);
101+
}
102+
103+
/// Create page object finder (custom implementation)
104+
Future<Finder> _createPageObjectFinder(FindElementModel model) async {
105+
final selectorMap = model.selectorMap!;
106+
final pageObjectName = selectorMap['pageObject'] as String;
107+
final elementModel = FindElementModel.fromJson(selectorMap['element']);
108+
109+
// This could be extended to load page objects from configuration
110+
// For now, just resolve the inner element
111+
print('Loading page object: $pageObjectName');
112+
return await _resolveFinder(elementModel);
113+
}
114+
115+
/// Create custom predicate finder
116+
Finder _createCustomPredicateFinder(FindElementModel model) {
117+
final selectorMap = model.selectorMap!;
118+
final predicateType = selectorMap['predicateType'] as String;
119+
final parameters = selectorMap['parameters'] as Map<String, dynamic>;
120+
121+
switch (predicateType) {
122+
case 'widget_property':
123+
return find.byWidgetPredicate(
124+
(widget) => _checkWidgetProperty(widget, parameters),
125+
skipOffstage: false,
126+
);
127+
case 'element_property':
128+
return find.byElementPredicate(
129+
(element) => _checkElementProperty(element, parameters),
130+
);
131+
default:
132+
throw ArgumentError('Unknown predicate type: $predicateType');
133+
}
134+
}
135+
136+
/// Recursively resolve any FindElementModel to a Finder
137+
Future<Finder> _resolveFinder(FindElementModel model) async {
138+
final strategy = ElementLookupStrategy.values
139+
.firstWhere((s) => s.name == model.strategy);
140+
return await strategy.toFinder(model);
141+
}
142+
143+
/// Helper methods for custom predicates
144+
bool _checkWidgetProperty(Widget widget, Map<String, dynamic> parameters) {
145+
final propertyName = parameters['property'] as String;
146+
final expectedValue = parameters['value'];
147+
148+
// This could be extended with reflection or a property registry
149+
switch (propertyName) {
150+
case 'runtimeType':
151+
return widget.runtimeType.toString() == expectedValue;
152+
case 'key':
153+
return widget.key?.toString() == expectedValue;
154+
default:
155+
return false;
156+
}
157+
}
158+
159+
bool _checkElementProperty(Element element, Map<String, dynamic> parameters) {
160+
final propertyName = parameters['property'] as String;
161+
final expectedValue = parameters['value'];
162+
163+
switch (propertyName) {
164+
case 'semanticsLabel':
165+
return element.renderObject?.debugSemantics?.label == expectedValue;
166+
default:
167+
return false;
168+
}
169+
}
170+
171+
/// Get icon by name (helper method)
172+
IconData _getIconByName(String iconName) {
173+
// This could be extended with a comprehensive icon registry
174+
switch (iconName.toLowerCase()) {
175+
case 'home':
176+
return Icons.home;
177+
case 'search':
178+
return Icons.search;
179+
case 'settings':
180+
return Icons.settings;
181+
case 'menu':
182+
return Icons.menu;
57183
default:
58-
return find.text(selector);
184+
return Icons.help_outline; // Default fallback
59185
}
60186
}
61187

@@ -77,18 +203,14 @@ extension ElementLookupStrategyExtension on ElementLookupStrategy {
77203
return '-flutter icon point';
78204
case ElementLookupStrategy.BY_ICON_NAME:
79205
return '-flutter icon name';
80-
// case ElementLookupStrategy.BY_ICON:
81-
// return '-flutter icon';
82-
// case ElementLookupStrategy.BY_ELEMENT_PREDICATE:
83-
// return '-flutter element predicate';
84-
// case ElementLookupStrategy.BY_SUBTYPE:
85-
// return '-flutter subtype';
86-
// case ElementLookupStrategy.BY_TYPE:
87-
// return '-flutter type';
88-
// case ElementLookupStrategy.BY_WIDGET:
89-
// return '-flutter widget';
90-
// case ElementLookupStrategy.BY_WIDGET_PREDICATE:
91-
// return '-flutter widget predicate';
206+
case ElementLookupStrategy.BY_ANCESTOR:
207+
return '-flutter ancestor';
208+
case ElementLookupStrategy.BY_DESCENDANT:
209+
return '-flutter descendant';
210+
case ElementLookupStrategy.BY_PAGE_OBJECT:
211+
return '-flutter page_object';
212+
case ElementLookupStrategy.BY_CUSTOM_PREDICATE:
213+
return '-flutter custom_predicate';
92214
}
93215
}
94216
}
Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,85 @@
11
class FindElementModel {
22
String strategy;
3-
String selector;
3+
dynamic selector; // Changed from String to dynamic to support complex finders
44
String? context;
5+
Map<String, dynamic>? parameters; // Additional parameters for complex finders
56

6-
FindElementModel(
7-
{required this.strategy, required this.selector, this.context});
7+
FindElementModel({
8+
required this.strategy,
9+
required this.selector,
10+
this.context,
11+
this.parameters,
12+
});
813

9-
factory FindElementModel.fromJson(Map<String, dynamic> json) =>
10-
FindElementModel(
11-
strategy: (json['strategy'] ?? json['using']) as String,
12-
selector: (json['selector'] ?? json['value']) as String,
13-
context: json['context'] as String?,
14-
);
14+
factory FindElementModel.fromJson(Map<String, dynamic> json) {
15+
return FindElementModel(
16+
strategy: (json['strategy'] ?? json['using']) as String,
17+
selector: json['selector'] ?? json['value'], // Can be String or Map
18+
context: json['context'] as String?,
19+
parameters: json['parameters'] as Map<String, dynamic>?,
20+
);
21+
}
1522

1623
Map<String, dynamic> toJson() => <String, dynamic>{
1724
'strategy': strategy,
1825
'selector': selector,
1926
"using": strategy,
2027
"value": selector,
2128
'context': context,
29+
'parameters': parameters,
2230
};
31+
32+
// /// Factory method for simple finders (backward compatibility)
33+
// factory FindElementModel.simple(String strategy, String selector,
34+
// {String? context}) {
35+
// return FindElementModel(
36+
// strategy: strategy,
37+
// selector: selector,
38+
// context: context,
39+
// );
40+
// }
41+
42+
// /// Factory method for ancestor finder
43+
// factory FindElementModel.ancestor({
44+
// required FindElementModel of,
45+
// required FindElementModel matching,
46+
// bool matchRoot = false,
47+
// String? context,
48+
// }) {
49+
// return FindElementModel(
50+
// strategy: 'ancestor',
51+
// selector: {
52+
// 'of': of.toJson(),
53+
// 'matching': matching.toJson(),
54+
// 'matchRoot': matchRoot,
55+
// },
56+
// context: context,
57+
// );
58+
// }
59+
60+
// /// Factory method for descendant finder
61+
// factory FindElementModel.descendant({
62+
// required FindElementModel of,
63+
// required FindElementModel matching,
64+
// bool matchRoot = false,
65+
// String? context,
66+
// }) {
67+
// return FindElementModel(
68+
// strategy: 'descendant',
69+
// selector: {
70+
// 'of': of.toJson(),
71+
// 'matching': matching.toJson(),
72+
// 'matchRoot': matchRoot,
73+
// },
74+
// context: context,
75+
// );
76+
// }
77+
78+
/// Get the selector as a Map (for complex finders)
79+
Map<String, dynamic>? get selectorMap => selector is Map<String, dynamic>
80+
? selector as Map<String, dynamic>
81+
: null;
82+
83+
/// Get the selector as a String (for simple finders)
84+
String? get selectorString => selector is String ? selector as String : null;
2385
}

server/lib/src/runner.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import 'package:package_info_plus/package_info_plus.dart';
1010
const MAX_TEST_DURATION_SECS = 24 * 60 * 60;
1111
// Need a better way to fetch this for automated release, this needs to be updated along with version bump
1212
// Can stay for now as it is not a breaking change
13-
const serverVersion = '0.0.29';
13+
const serverVersion = '0.0.30';
1414

1515
Future<void> initializeTest({Widget? app, Function? callback}) async {
1616
IntegrationTestWidgetsFlutterBinding binding =

server/lib/src/server.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class FlutterServer {
7272
_registerPost(ClearHandler("/session/<sessionId>/element/<id>/clear"));
7373
_registerPost(NewSessionHandler("/session"));
7474
_registerPost(FindElementHandler("/session/<sessionId>/element"));
75-
_registerPost(FindElementstHandler("/session/<sessionId>/elements"));
75+
_registerPost(FindElementsHandler("/session/<sessionId>/elements"));
7676
_registerPost(
7777
RenderTreeHandler("/session/<sessionId>/element/render_tree"));
7878
_registerPost(ClickHandler("/session/<sessionId>/element/<id>/click"));

0 commit comments

Comments
 (0)