calabash-android 0.4.6 → 0.4.7.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/calabash-android.gemspec +1 -0
- data/doc/calabash-android-help.txt +6 -2
- data/lib/calabash-android/lib/TestServer.apk +0 -0
- data/lib/calabash-android/operations.rb +86 -14
- data/lib/calabash-android/steps/navigation_steps.rb +0 -0
- data/lib/calabash-android/version.rb +1 -1
- data/test-server/instrumentation-backend/.classpath +1 -0
- data/test-server/instrumentation-backend/project.properties +1 -1
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/FranklyResult.java +2 -9
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/HttpServer.java +41 -7
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/ViewDump.java +50 -0
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/softkey/PressMenu.java +0 -0
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/softkey/SelectFromMenuByText.java +0 -0
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/json/JSONUtils.java +18 -0
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/Query.java +1 -2
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/ViewMapper.java +42 -24
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/ast/PartialFutureList.java +2 -2
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/ast/UIQueryUtils.java +222 -9
- metadata +37 -35
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 64cf7a26d360f3483fdc88f29c24f82d995e4f59
|
4
|
+
data.tar.gz: b52dacd37f6444b16af0fa62c8453ea5b9e13fc9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 07241d9e4d9b291bd9ccdd277badfbde8fc7ad774cc7491a8d960762d72e19b41985950b1d3554d693debca272373b7a0783d118461067711dc70b40fb91da33
|
7
|
+
data.tar.gz: 5988a802d01af1d3144c7ff391480dc28c34957b23081f0686a80789f9532dab480a258f0d7a74df9cbf7fc9beb5e86b80773c048dc41c5abd4879e5afb683a4
|
data/calabash-android.gemspec
CHANGED
@@ -5,6 +5,7 @@ Usage: calabash-android <command-name> [parameters] [options]
|
|
5
5
|
setup
|
6
6
|
build
|
7
7
|
run [parameters]
|
8
|
+
console
|
8
9
|
version
|
9
10
|
|
10
11
|
Commands:
|
@@ -15,11 +16,14 @@ Usage: calabash-android <command-name> [parameters] [options]
|
|
15
16
|
setup sets up a non-default keystore to use with this test project.
|
16
17
|
|
17
18
|
build builds the test server that will be used when testing the app.
|
18
|
-
|
19
|
+
|
19
20
|
run runs Cucumber in the current folder with the enviroment needed.
|
20
21
|
|
22
|
+
console opens an interactive irb shell. Here you can send commands
|
23
|
+
to the app and query for UI elements.
|
24
|
+
|
21
25
|
version prints the gem version
|
22
26
|
|
23
27
|
|
24
28
|
Options:
|
25
|
-
-v, --verbose Turns on verbose logging
|
29
|
+
-v, --verbose Turns on verbose logging
|
Binary file
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'httpclient'
|
1
2
|
require 'json'
|
2
3
|
require 'net/http'
|
3
4
|
require 'open-uri'
|
@@ -254,15 +255,86 @@ module Operations
|
|
254
255
|
|
255
256
|
def http(path, data = {}, options = {})
|
256
257
|
begin
|
257
|
-
|
258
|
-
http
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
258
|
+
|
259
|
+
configure_http(@http, options)
|
260
|
+
make_http_request(
|
261
|
+
:method => :post,
|
262
|
+
:body => data.to_json,
|
263
|
+
:uri => url_for(path),
|
264
|
+
:header => {"Content-Type" => "application/json;charset=utf-8"})
|
265
|
+
|
266
|
+
rescue HTTPClient::TimeoutError,
|
267
|
+
HTTPClient::KeepAliveDisconnected,
|
268
|
+
Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED,
|
269
|
+
Errno::ETIMEDOUT => e
|
270
|
+
log "It looks like your app is no longer running. \nIt could be because of a crash or because your test script shut it down."
|
271
|
+
raise e
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def set_http(http)
|
276
|
+
@http = http
|
277
|
+
end
|
278
|
+
|
279
|
+
def url_for(method)
|
280
|
+
url = URI.parse(ENV['DEVICE_ENDPOINT']|| "http://127.0.0.1:#{@server_port}")
|
281
|
+
path = url.path
|
282
|
+
if path.end_with? "/"
|
283
|
+
path = "#{path}#{method}"
|
284
|
+
else
|
285
|
+
path = "#{path}/#{method}"
|
265
286
|
end
|
287
|
+
url.path = path
|
288
|
+
url
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
|
293
|
+
def make_http_request(options)
|
294
|
+
body = nil
|
295
|
+
begin
|
296
|
+
unless @http
|
297
|
+
@http = init_request(options)
|
298
|
+
end
|
299
|
+
header = options[:header] || {}
|
300
|
+
header["Content-Type"] = "application/json;charset=utf-8"
|
301
|
+
options[:header] = header
|
302
|
+
|
303
|
+
if options[:method] == :post
|
304
|
+
body = @http.post(options[:uri], options).body
|
305
|
+
else
|
306
|
+
body = @http.get(options[:uri], options).body
|
307
|
+
end
|
308
|
+
rescue Exception => e
|
309
|
+
if @http
|
310
|
+
@http.reset_all
|
311
|
+
@http=nil
|
312
|
+
end
|
313
|
+
raise e
|
314
|
+
end
|
315
|
+
body
|
316
|
+
end
|
317
|
+
|
318
|
+
def init_request(options)
|
319
|
+
http = HTTPClient.new
|
320
|
+
configure_http(http, options)
|
321
|
+
end
|
322
|
+
|
323
|
+
def configure_http(http, options)
|
324
|
+
return unless http
|
325
|
+
http.connect_timeout = options[:open_timeout] || 15
|
326
|
+
http.send_timeout = options[:send_timeout] || 15
|
327
|
+
http.receive_timeout = options[:read_timeout] || 15
|
328
|
+
if options.has_key?(:debug) && options[:debug]
|
329
|
+
http.debug_dev= $stdout
|
330
|
+
else
|
331
|
+
if ENV['DEBUG_HTTP'] and (ENV['DEBUG_HTTP'] != '0')
|
332
|
+
http.debug_dev = $stdout
|
333
|
+
else
|
334
|
+
http.debug_dev= nil
|
335
|
+
end
|
336
|
+
end
|
337
|
+
http
|
266
338
|
end
|
267
339
|
|
268
340
|
def screenshot(options={:prefix => nil, :name => nil})
|
@@ -493,8 +565,8 @@ module Operations
|
|
493
565
|
performAction("touch_coordinate", center_x, center_y)
|
494
566
|
end
|
495
567
|
|
496
|
-
def http(
|
497
|
-
default_device.http(
|
568
|
+
def http(path, data = {}, options = {})
|
569
|
+
default_device.http(path, data, options)
|
498
570
|
end
|
499
571
|
|
500
572
|
def html(q)
|
@@ -620,12 +692,12 @@ module Operations
|
|
620
692
|
res['results']
|
621
693
|
end
|
622
694
|
|
623
|
-
def url_for(
|
624
|
-
|
695
|
+
def url_for( method )
|
696
|
+
default_device.url_for(method)
|
625
697
|
end
|
626
698
|
|
627
|
-
def make_http_request(
|
628
|
-
|
699
|
+
def make_http_request(options)
|
700
|
+
default_device.make_http_request(options)
|
629
701
|
end
|
630
702
|
|
631
703
|
end
|
File without changes
|
@@ -6,5 +6,6 @@
|
|
6
6
|
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
7
7
|
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
8
8
|
<classpathentry kind="lib" path="libs/robotium-solo-3.6.jar"/>
|
9
|
+
<classpathentry kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
9
10
|
<classpathentry kind="output" path="bin/classes"/>
|
10
11
|
</classpath>
|
data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/FranklyResult.java
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
package sh.calaba.instrumentationbackend;
|
2
2
|
|
3
3
|
import java.io.CharArrayWriter;
|
4
|
-
import java.io.IOException;
|
5
4
|
import java.io.PrintWriter;
|
6
5
|
import java.util.Collections;
|
7
6
|
import java.util.HashMap;
|
8
7
|
import java.util.List;
|
9
8
|
import java.util.Map;
|
10
9
|
|
11
|
-
import sh.calaba.
|
10
|
+
import sh.calaba.instrumentationbackend.json.JSONUtils;
|
12
11
|
|
13
12
|
/**
|
14
13
|
* Represents a response in the Frankly protocol.
|
@@ -52,13 +51,7 @@ public class FranklyResult {
|
|
52
51
|
}
|
53
52
|
|
54
53
|
public String asJson() {
|
55
|
-
|
56
|
-
|
57
|
-
try {
|
58
|
-
return mapper.writeValueAsString(asMap());
|
59
|
-
} catch (IOException e) {
|
60
|
-
throw new RuntimeException("Could not convert result to json", e);
|
61
|
-
}
|
54
|
+
return JSONUtils.asJson(asMap());
|
62
55
|
}
|
63
56
|
|
64
57
|
public Map<String,Object> asMap()
|
@@ -3,7 +3,6 @@ package sh.calaba.instrumentationbackend.actions;
|
|
3
3
|
import java.io.ByteArrayInputStream;
|
4
4
|
import java.io.ByteArrayOutputStream;
|
5
5
|
import java.io.File;
|
6
|
-
import java.io.IOException;
|
7
6
|
import java.io.PrintWriter;
|
8
7
|
import java.io.StringWriter;
|
9
8
|
import java.util.List;
|
@@ -17,8 +16,8 @@ import sh.calaba.instrumentationbackend.Command;
|
|
17
16
|
import sh.calaba.instrumentationbackend.FranklyResult;
|
18
17
|
import sh.calaba.instrumentationbackend.InstrumentationBackend;
|
19
18
|
import sh.calaba.instrumentationbackend.Result;
|
19
|
+
import sh.calaba.instrumentationbackend.json.JSONUtils;
|
20
20
|
import sh.calaba.instrumentationbackend.query.Query;
|
21
|
-
import sh.calaba.instrumentationbackend.query.QueryResult;
|
22
21
|
import sh.calaba.org.codehaus.jackson.map.DeserializationConfig.Feature;
|
23
22
|
import sh.calaba.org.codehaus.jackson.map.ObjectMapper;
|
24
23
|
import android.graphics.Bitmap;
|
@@ -61,14 +60,50 @@ public class HttpServer extends NanoHTTPD {
|
|
61
60
|
super(testServerPort, new File("/"));
|
62
61
|
}
|
63
62
|
|
64
|
-
@SuppressWarnings("rawtypes")
|
63
|
+
@SuppressWarnings({ "rawtypes", "unchecked" })
|
65
64
|
public Response serve(String uri, String method, Properties header,
|
66
65
|
Properties params, Properties files) {
|
67
66
|
System.out.println("URI: " + uri);
|
68
67
|
if (uri.endsWith("/ping")) {
|
69
68
|
return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, "pong");
|
70
69
|
|
71
|
-
}
|
70
|
+
}
|
71
|
+
else if (uri.endsWith("/dump")) {
|
72
|
+
FranklyResult errorResult = null;
|
73
|
+
try {
|
74
|
+
|
75
|
+
|
76
|
+
String json = params.getProperty("json");
|
77
|
+
|
78
|
+
|
79
|
+
if (json == null)
|
80
|
+
{
|
81
|
+
Map<?,?> dumpTree = new ViewDump().dumpWithoutElements();
|
82
|
+
return new NanoHTTPD.Response(HTTP_OK, "application/json;charset=utf-8", JSONUtils.asJson(dumpTree));
|
83
|
+
}
|
84
|
+
else
|
85
|
+
{
|
86
|
+
ObjectMapper mapper = new ObjectMapper();
|
87
|
+
Map dumpSpec = mapper.readValue(json, Map.class);
|
88
|
+
|
89
|
+
List<Integer> path = (List<Integer>) dumpSpec.get("path");
|
90
|
+
if (path == null)
|
91
|
+
{
|
92
|
+
Map<?,?> dumpTree = new ViewDump().dumpWithoutElements();
|
93
|
+
return new NanoHTTPD.Response(HTTP_OK, "application/json;charset=utf-8", JSONUtils.asJson(dumpTree));
|
94
|
+
}
|
95
|
+
Map<?,?> dumpTree = new ViewDump().dumpPathWithoutElements(path);
|
96
|
+
return new NanoHTTPD.Response(HTTP_OK, "application/json;charset=utf-8", JSONUtils.asJson(dumpTree));
|
97
|
+
}
|
98
|
+
|
99
|
+
|
100
|
+
} catch (Exception e ) {
|
101
|
+
e.printStackTrace();
|
102
|
+
errorResult = FranklyResult.fromThrowable(e);
|
103
|
+
}
|
104
|
+
return new NanoHTTPD.Response(HTTP_INTERNALERROR, "application/json;charset=utf-8", errorResult.asJson());
|
105
|
+
}
|
106
|
+
else if (uri.endsWith("/map")) {
|
72
107
|
FranklyResult errorResult = null;
|
73
108
|
try {
|
74
109
|
String commandString = params.getProperty("json");
|
@@ -77,12 +112,11 @@ public class HttpServer extends NanoHTTPD {
|
|
77
112
|
|
78
113
|
String uiQuery = (String) command.get("query");
|
79
114
|
Map op = (Map) command.get("operation");
|
115
|
+
@SuppressWarnings("unused") //TODO: support other methods, e.g., flash
|
80
116
|
String methodName = (String) op.get("method_name");
|
81
117
|
List arguments = (List) op.get("arguments");
|
82
118
|
|
83
|
-
//For now we only support query
|
84
|
-
//query_all includes also invisible views, while query filters them
|
85
|
-
boolean includeInVisible = "query_all".equals(methodName);
|
119
|
+
//For now we only support query
|
86
120
|
|
87
121
|
|
88
122
|
List queryResult = new Query(uiQuery,arguments).executeQuery();
|
data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/ViewDump.java
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
package sh.calaba.instrumentationbackend.actions;
|
2
|
+
|
3
|
+
import java.util.ArrayList;
|
4
|
+
import java.util.List;
|
5
|
+
import java.util.Map;
|
6
|
+
import java.util.concurrent.Callable;
|
7
|
+
import java.util.concurrent.atomic.AtomicReference;
|
8
|
+
|
9
|
+
import sh.calaba.instrumentationbackend.query.ast.UIQueryUtils;
|
10
|
+
|
11
|
+
@SuppressWarnings({ "rawtypes", "unchecked" })
|
12
|
+
public class ViewDump {
|
13
|
+
|
14
|
+
public Map<?,?> dumpWithoutElements() {
|
15
|
+
Map<?, ?> dumpTree = (Map) UIQueryUtils.evaluateSyncInMainThread(new Callable() {
|
16
|
+
public Object call() throws Exception {
|
17
|
+
return UIQueryUtils.dump();
|
18
|
+
}
|
19
|
+
});
|
20
|
+
|
21
|
+
return sameTreeWithoutElements(dumpTree);
|
22
|
+
|
23
|
+
}
|
24
|
+
|
25
|
+
|
26
|
+
public Map<?,?> dumpPathWithoutElements(final List<Integer> path) {
|
27
|
+
final AtomicReference<List<Integer>> ref = new AtomicReference<List<Integer>>(path);
|
28
|
+
Map<?, ?> dumpTree = (Map) UIQueryUtils.evaluateSyncInMainThread(new Callable() {
|
29
|
+
public Object call() throws Exception {
|
30
|
+
return UIQueryUtils.dumpByPath(ref.get());
|
31
|
+
}
|
32
|
+
});
|
33
|
+
|
34
|
+
return UIQueryUtils.mapWithElAsNull(dumpTree);
|
35
|
+
}
|
36
|
+
|
37
|
+
|
38
|
+
private Map<?, ?> sameTreeWithoutElements(Map<?, ?> dump) {
|
39
|
+
Map node = UIQueryUtils.mapWithElAsNull(dump);
|
40
|
+
List<Map> nodeChildren = (List<Map>) node.get("children");
|
41
|
+
List<Map> childrenNoEl = new ArrayList<Map>(nodeChildren.size());
|
42
|
+
for (Map child : nodeChildren) {
|
43
|
+
childrenNoEl.add(sameTreeWithoutElements(child));
|
44
|
+
}
|
45
|
+
node.put("children",childrenNoEl);
|
46
|
+
return node;
|
47
|
+
}
|
48
|
+
|
49
|
+
|
50
|
+
}
|
File without changes
|
File without changes
|
data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/json/JSONUtils.java
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
package sh.calaba.instrumentationbackend.json;
|
2
|
+
|
3
|
+
import java.io.IOException;
|
4
|
+
import java.util.Map;
|
5
|
+
|
6
|
+
import sh.calaba.org.codehaus.jackson.map.ObjectMapper;
|
7
|
+
|
8
|
+
public class JSONUtils {
|
9
|
+
|
10
|
+
public static String asJson(Map<?,?> map) {
|
11
|
+
ObjectMapper mapper = new ObjectMapper();
|
12
|
+
try {
|
13
|
+
return mapper.writeValueAsString(map);
|
14
|
+
} catch (IOException e) {
|
15
|
+
throw new RuntimeException("Could not convert result to json: "+map, e);
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/Query.java
CHANGED
@@ -164,8 +164,7 @@ public class Query {
|
|
164
164
|
Set<View> parents = new HashSet<View>();
|
165
165
|
for (View v : viewFetcher.getAllViews(false))
|
166
166
|
{
|
167
|
-
View parent = viewFetcher.getTopParent(v);
|
168
|
-
System.out.println(parent);
|
167
|
+
View parent = viewFetcher.getTopParent(v);
|
169
168
|
parents.add(parent);
|
170
169
|
}
|
171
170
|
List<View> results = new ArrayList<View>();
|
data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/ViewMapper.java
CHANGED
@@ -18,33 +18,14 @@ public class ViewMapper {
|
|
18
18
|
public static Object extractDataFromView(View v) {
|
19
19
|
|
20
20
|
Map data = new HashMap();
|
21
|
-
data.put("class", v
|
21
|
+
data.put("class", getClassNameForView(v));
|
22
22
|
data.put("description", v.toString());
|
23
|
-
|
24
|
-
data.put("contentDescription", description != null ? description.toString() : null);
|
23
|
+
data.put("contentDescription", getContentDescriptionForView(v));
|
25
24
|
data.put("enabled", v.isEnabled());
|
26
|
-
String id = null;
|
27
|
-
try {
|
28
|
-
id = InstrumentationBackend.solo.getCurrentActivity()
|
29
|
-
.getResources().getResourceEntryName(v.getId());
|
30
|
-
} catch (Resources.NotFoundException e) {
|
31
|
-
System.out.println("Resource not found for " + v.getId()
|
32
|
-
+ ". Moving on.");
|
33
|
-
}
|
34
|
-
data.put("id", id);
|
35
|
-
|
36
|
-
Map rect = new HashMap();
|
37
|
-
int[] location = new int[2];
|
38
|
-
v.getLocationOnScreen(location);
|
39
|
-
|
40
|
-
rect.put("x", location[0]);
|
41
|
-
rect.put("y", location[1]);
|
42
25
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
rect.put("width", v.getWidth());
|
47
|
-
rect.put("height", v.getHeight());
|
26
|
+
data.put("id", getIdForView(v));
|
27
|
+
|
28
|
+
Map rect = getRectForView(v);
|
48
29
|
|
49
30
|
data.put("rect", rect);
|
50
31
|
|
@@ -64,6 +45,43 @@ public class ViewMapper {
|
|
64
45
|
|
65
46
|
}
|
66
47
|
|
48
|
+
public static Map getRectForView(View v) {
|
49
|
+
Map rect = new HashMap();
|
50
|
+
int[] location = new int[2];
|
51
|
+
v.getLocationOnScreen(location);
|
52
|
+
|
53
|
+
rect.put("x", location[0]);
|
54
|
+
rect.put("y", location[1]);
|
55
|
+
|
56
|
+
rect.put("center_x", location[0] + v.getWidth()/2.0);
|
57
|
+
rect.put("center_y", location[1] + v.getHeight()/2.0);
|
58
|
+
|
59
|
+
rect.put("width", v.getWidth());
|
60
|
+
rect.put("height", v.getHeight());
|
61
|
+
return rect;
|
62
|
+
}
|
63
|
+
|
64
|
+
public static String getContentDescriptionForView(View v) {
|
65
|
+
CharSequence description = v.getContentDescription();
|
66
|
+
return description != null ? description.toString() : null;
|
67
|
+
}
|
68
|
+
|
69
|
+
public static String getClassNameForView(View v) {
|
70
|
+
return v.getClass().getName();
|
71
|
+
}
|
72
|
+
|
73
|
+
public static String getIdForView(View v) {
|
74
|
+
String id = null;
|
75
|
+
try {
|
76
|
+
id = InstrumentationBackend.solo.getCurrentActivity()
|
77
|
+
.getResources().getResourceEntryName(v.getId());
|
78
|
+
} catch (Resources.NotFoundException e) {
|
79
|
+
System.out.println("Resource not found for " + v.getId()
|
80
|
+
+ ". Moving on.");
|
81
|
+
}
|
82
|
+
return id;
|
83
|
+
}
|
84
|
+
|
67
85
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
68
86
|
public static Object mapView(Object o) {
|
69
87
|
if (o instanceof View) {
|
@@ -86,8 +86,8 @@ public class PartialFutureList implements Future {
|
|
86
86
|
return result;
|
87
87
|
}
|
88
88
|
|
89
|
-
private Future extractFuture(List
|
90
|
-
for (Object o :
|
89
|
+
private Future extractFuture(List list) {
|
90
|
+
for (Object o : list) {
|
91
91
|
if (o instanceof Future) {
|
92
92
|
return (Future) o;
|
93
93
|
}
|
@@ -19,12 +19,17 @@ import org.antlr.runtime.tree.CommonTree;
|
|
19
19
|
import sh.calaba.instrumentationbackend.InstrumentationBackend;
|
20
20
|
import sh.calaba.instrumentationbackend.actions.webview.QueryHelper;
|
21
21
|
import sh.calaba.instrumentationbackend.query.CompletedFuture;
|
22
|
+
import sh.calaba.instrumentationbackend.query.Query;
|
23
|
+
import sh.calaba.instrumentationbackend.query.ViewMapper;
|
22
24
|
import sh.calaba.instrumentationbackend.query.antlr.UIQueryParser;
|
23
25
|
import sh.calaba.org.codehaus.jackson.map.ObjectMapper;
|
24
26
|
import sh.calaba.org.codehaus.jackson.type.TypeReference;
|
25
|
-
import android.
|
27
|
+
import android.text.InputType;
|
26
28
|
import android.view.View;
|
27
29
|
import android.webkit.WebView;
|
30
|
+
import android.widget.Button;
|
31
|
+
import android.widget.CheckBox;
|
32
|
+
import android.widget.TextView;
|
28
33
|
|
29
34
|
public class UIQueryUtils {
|
30
35
|
|
@@ -150,7 +155,7 @@ public class UIQueryUtils {
|
|
150
155
|
if (!(v instanceof View)) { return true; }
|
151
156
|
View view = (View) v;
|
152
157
|
|
153
|
-
if (view.
|
158
|
+
if (view.getHeight() == 0 || view.getWidth() == 0) {
|
154
159
|
return false;
|
155
160
|
}
|
156
161
|
|
@@ -158,13 +163,7 @@ public class UIQueryUtils {
|
|
158
163
|
}
|
159
164
|
|
160
165
|
public static String getId(View view) {
|
161
|
-
|
162
|
-
return InstrumentationBackend.solo.getCurrentActivity()
|
163
|
-
.getResources().getResourceEntryName(view.getId());
|
164
|
-
|
165
|
-
}
|
166
|
-
catch (NotFoundException e) {}
|
167
|
-
return null;
|
166
|
+
return ViewMapper.getIdForView(view);
|
168
167
|
}
|
169
168
|
|
170
169
|
@SuppressWarnings("rawtypes")
|
@@ -274,4 +273,218 @@ public class UIQueryUtils {
|
|
274
273
|
|
275
274
|
}
|
276
275
|
|
276
|
+
/*
|
277
|
+
*
|
278
|
+
{"rect"=>{"x"=>0, "y"=>0, "width"=>768, "height"=>1024},
|
279
|
+
"hit-point"=>{"x"=>384, "y"=>512},
|
280
|
+
"id"=>"",
|
281
|
+
"action"=>false,
|
282
|
+
"enabled"=>1,
|
283
|
+
"visible"=>1,
|
284
|
+
"value"=>nil,
|
285
|
+
"type"=>"[object UIAWindow]",
|
286
|
+
"name"=>nil,
|
287
|
+
"label"=>nil,
|
288
|
+
"children"=> [(samestructure)*]
|
289
|
+
|
290
|
+
*/
|
291
|
+
public static Map<?,?> dump()
|
292
|
+
{
|
293
|
+
Query dummyQuery = new Query("not_used");
|
294
|
+
|
295
|
+
return dumpRecursively(emptyRootView(), dummyQuery.rootViews());
|
296
|
+
}
|
297
|
+
|
298
|
+
@SuppressWarnings({ "unchecked", "rawtypes" })
|
299
|
+
public static Map<?,?> mapWithElAsNull(Map<?,?> dump) {
|
300
|
+
HashMap result = new HashMap(dump);
|
301
|
+
result.put("el",null);
|
302
|
+
return result;
|
303
|
+
}
|
304
|
+
|
305
|
+
|
306
|
+
|
307
|
+
@SuppressWarnings({ "rawtypes", "unchecked" })
|
308
|
+
protected static Map<?,?> dumpRecursively(Map parentView,List<View> children)
|
309
|
+
{
|
310
|
+
ArrayList childrenArray = new ArrayList(32);
|
311
|
+
for (int i=0;i<children.size();i++) {
|
312
|
+
View view = children.get(i);
|
313
|
+
Map serializedChild = serializeViewToDump(view);
|
314
|
+
List<Integer> childPath = new ArrayList<Integer>((List) parentView.get("path"));
|
315
|
+
childPath.add(i);
|
316
|
+
serializedChild.put("path", childPath);
|
317
|
+
childrenArray.add(dumpRecursively(serializedChild, UIQueryUtils.subviews(view)));
|
318
|
+
}
|
319
|
+
|
320
|
+
parentView.put("children", childrenArray);
|
321
|
+
|
322
|
+
return parentView;
|
323
|
+
}
|
324
|
+
|
325
|
+
@SuppressWarnings({ "rawtypes", "unchecked" })
|
326
|
+
public static Map<?,?> dumpByPath(List<Integer> path) {
|
327
|
+
Query dummyQuery = new Query("not_used");
|
328
|
+
|
329
|
+
Map currentView = emptyRootView();
|
330
|
+
List<View> currentChildren = dummyQuery.rootViews();
|
331
|
+
|
332
|
+
for (Integer i:path) {
|
333
|
+
View child = currentChildren.get(i);
|
334
|
+
currentView = serializeViewToDump(child);
|
335
|
+
currentChildren = UIQueryUtils.subviews(child);
|
336
|
+
}
|
337
|
+
|
338
|
+
return currentView;
|
339
|
+
}
|
340
|
+
|
341
|
+
@SuppressWarnings({ "rawtypes", "unchecked" })
|
342
|
+
public static Map<?,?> serializeViewToDump(View view) {
|
343
|
+
if (view == null) {return null;}
|
344
|
+
|
345
|
+
Map m = new HashMap();
|
346
|
+
|
347
|
+
m.put("id",getId(view));
|
348
|
+
m.put("el",view);
|
349
|
+
|
350
|
+
Map rect = ViewMapper.getRectForView(view);
|
351
|
+
Map hitPoint = extractHitPointFromRect(rect);
|
352
|
+
|
353
|
+
m.put("rect",rect);
|
354
|
+
m.put("hit-point",hitPoint);
|
355
|
+
m.put("action",actionForView(view));
|
356
|
+
m.put("enabled",view.isEnabled());
|
357
|
+
m.put("visible",isVisible(view));
|
358
|
+
m.put("entry_types", elementEntryTypes(view));
|
359
|
+
m.put("value",extractValueFromView(view));
|
360
|
+
m.put("type",ViewMapper.getClassNameForView(view));
|
361
|
+
m.put("name",getNameForView(view));
|
362
|
+
m.put("label",ViewMapper.getContentDescriptionForView(view));
|
363
|
+
return m;
|
364
|
+
}
|
365
|
+
|
366
|
+
public static List<String> elementEntryTypes(View view) {
|
367
|
+
if (view instanceof TextView)
|
368
|
+
{
|
369
|
+
TextView textView = (TextView) view;
|
370
|
+
return mapTextViewInputTypes(textView.getInputType());
|
371
|
+
}
|
372
|
+
return null;
|
373
|
+
|
374
|
+
}
|
375
|
+
|
376
|
+
public static List<String> mapTextViewInputTypes(int inputType) {
|
377
|
+
List<String> inputTypes = new ArrayList<String>();
|
378
|
+
if (inputTypeHasTrait(inputType, InputType.TYPE_TEXT_VARIATION_PASSWORD) || inputTypeHasTrait(inputType, InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) {
|
379
|
+
inputTypes.add("password");
|
380
|
+
}
|
381
|
+
if (inputTypeHasTrait(inputType, InputType.TYPE_CLASS_NUMBER)) {
|
382
|
+
inputTypes.add("numeric");
|
383
|
+
}
|
384
|
+
inputTypes.add(String.valueOf(inputType));
|
385
|
+
|
386
|
+
return inputTypes;
|
387
|
+
}
|
388
|
+
|
389
|
+
private static boolean inputTypeHasTrait(int inputType,
|
390
|
+
int inputTypeTrait) {
|
391
|
+
return (inputType & inputTypeTrait) != 0;
|
392
|
+
}
|
393
|
+
|
394
|
+
private static Object getNameForView(View view)
|
395
|
+
{
|
396
|
+
Object result = null;
|
397
|
+
Method hintMethod = hasProperty(view,"hint");
|
398
|
+
if (hintMethod!=null)
|
399
|
+
{
|
400
|
+
result = getProperty(view, hintMethod);
|
401
|
+
}
|
402
|
+
if (result != null) {return result.toString();}
|
403
|
+
Method textMethod = hasProperty(view,"text");
|
404
|
+
if (textMethod!=null)
|
405
|
+
{
|
406
|
+
result = getProperty(view, textMethod);
|
407
|
+
}
|
408
|
+
if (result != null) {return result.toString();}
|
409
|
+
|
410
|
+
return null;
|
411
|
+
}
|
412
|
+
|
413
|
+
public static Object extractValueFromView(View view) {
|
414
|
+
if (view instanceof Button) {
|
415
|
+
Button b = (Button) view;
|
416
|
+
return b.getText().toString();
|
417
|
+
}
|
418
|
+
else if (view instanceof CheckBox) {
|
419
|
+
CheckBox c = (CheckBox) view;
|
420
|
+
return c.isChecked();
|
421
|
+
}
|
422
|
+
else if (view instanceof TextView) {
|
423
|
+
TextView t = (TextView) view;
|
424
|
+
return t.getText().toString();
|
425
|
+
}
|
426
|
+
return null;
|
427
|
+
}
|
428
|
+
|
429
|
+
/*
|
430
|
+
* function action(el)
|
431
|
+
{
|
432
|
+
var normalized = normalize(el);
|
433
|
+
if (!normalized) {
|
434
|
+
return false;
|
435
|
+
}
|
436
|
+
if (normalized instanceof UIAButton) {
|
437
|
+
return {
|
438
|
+
"type":'touch',
|
439
|
+
"gesture":'tap'
|
440
|
+
};
|
441
|
+
}
|
442
|
+
//TODO MORE
|
443
|
+
return false;
|
444
|
+
}
|
445
|
+
*/
|
446
|
+
@SuppressWarnings({ "rawtypes", "unchecked" })
|
447
|
+
public static Map<?,?> actionForView(View view)
|
448
|
+
{
|
449
|
+
Map result = null;
|
450
|
+
if (view instanceof android.widget.Button || view instanceof android.widget.ImageButton) {
|
451
|
+
result = new HashMap();
|
452
|
+
result.put("type","touch");
|
453
|
+
result.put("gesture","tap");
|
454
|
+
}
|
455
|
+
|
456
|
+
//TODO: obviously many more!
|
457
|
+
return result;
|
458
|
+
|
459
|
+
|
460
|
+
}
|
461
|
+
|
462
|
+
@SuppressWarnings({ "rawtypes", "unchecked" })
|
463
|
+
public static Map extractHitPointFromRect(Map rect) {
|
464
|
+
Map hitPoint = new HashMap();
|
465
|
+
hitPoint.put("x", rect.get("center_x"));
|
466
|
+
hitPoint.put("y", rect.get("center_y"));
|
467
|
+
return hitPoint;
|
468
|
+
}
|
469
|
+
|
470
|
+
@SuppressWarnings({"unchecked", "rawtypes", "serial"})
|
471
|
+
private static Map<?,?> emptyRootView() {
|
472
|
+
return new HashMap() {{
|
473
|
+
put("id",null);
|
474
|
+
put("el",null);
|
475
|
+
put("rect",null);
|
476
|
+
put("hit-point",null);
|
477
|
+
put("action",false);
|
478
|
+
put("enabled",false);
|
479
|
+
put("visible",true);
|
480
|
+
put("value",null);
|
481
|
+
put("path",new ArrayList<Integer>());
|
482
|
+
put("type","[object CalabashRootView]");
|
483
|
+
put("name",null);
|
484
|
+
put("label",null);
|
485
|
+
}};
|
486
|
+
}
|
487
|
+
|
488
|
+
|
489
|
+
|
277
490
|
}
|
metadata
CHANGED
@@ -1,113 +1,114 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: calabash-android
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.7.pre1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Jonas Maturana Larsen
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-05
|
11
|
+
date: 2013-06-05 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: cucumber
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: json
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - '>='
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - '>='
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '0'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: retriable
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - '>='
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '0'
|
54
48
|
type: :runtime
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - '>='
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: '0'
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: slowhandcuke
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
|
-
- -
|
59
|
+
- - '>='
|
68
60
|
- !ruby/object:Gem::Version
|
69
61
|
version: '0'
|
70
62
|
type: :runtime
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
|
-
- -
|
66
|
+
- - '>='
|
76
67
|
- !ruby/object:Gem::Version
|
77
68
|
version: '0'
|
78
69
|
- !ruby/object:Gem::Dependency
|
79
70
|
name: rubyzip
|
80
71
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
72
|
requirements:
|
83
|
-
- -
|
73
|
+
- - '>='
|
84
74
|
- !ruby/object:Gem::Version
|
85
75
|
version: '0'
|
86
76
|
type: :runtime
|
87
77
|
prerelease: false
|
88
78
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
79
|
requirements:
|
91
|
-
- -
|
80
|
+
- - '>='
|
92
81
|
- !ruby/object:Gem::Version
|
93
82
|
version: '0'
|
94
83
|
- !ruby/object:Gem::Dependency
|
95
84
|
name: awesome_print
|
96
85
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
86
|
requirements:
|
99
|
-
- -
|
87
|
+
- - '>='
|
100
88
|
- !ruby/object:Gem::Version
|
101
89
|
version: '0'
|
102
90
|
type: :runtime
|
103
91
|
prerelease: false
|
104
92
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
93
|
requirements:
|
107
|
-
- -
|
94
|
+
- - '>='
|
108
95
|
- !ruby/object:Gem::Version
|
109
96
|
version: '0'
|
110
|
-
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: httpclient
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.3.2
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.3.2
|
111
|
+
description: 'calabash-android drives tests for native and hybrid Android apps. '
|
111
112
|
email:
|
112
113
|
- jonas@lesspainful.com
|
113
114
|
executables:
|
@@ -296,6 +297,7 @@ files:
|
|
296
297
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/HttpServer.java
|
297
298
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/NanoHTTPD.java
|
298
299
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/NullAction.java
|
300
|
+
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/ViewDump.java
|
299
301
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/activity/FinishOpenedActivities.java
|
300
302
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/activity/GetOpenedActivities.java
|
301
303
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/activity/GoBackToActivity.java
|
@@ -398,6 +400,7 @@ files:
|
|
398
400
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/webview/ScrollTo.java
|
399
401
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/webview/SetPropertyByCssSelector.java
|
400
402
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/webview/SetText.java
|
403
|
+
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/json/JSONUtils.java
|
401
404
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/CompletedFuture.java
|
402
405
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/InvocationOperation.java
|
403
406
|
- test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/Operation.java
|
@@ -836,26 +839,25 @@ files:
|
|
836
839
|
- lib/calabash-android/lib/TestServer.apk
|
837
840
|
homepage: http://github.com/calabash
|
838
841
|
licenses: []
|
842
|
+
metadata: {}
|
839
843
|
post_install_message:
|
840
844
|
rdoc_options: []
|
841
845
|
require_paths:
|
842
846
|
- lib
|
843
847
|
required_ruby_version: !ruby/object:Gem::Requirement
|
844
|
-
none: false
|
845
848
|
requirements:
|
846
|
-
- -
|
849
|
+
- - '>='
|
847
850
|
- !ruby/object:Gem::Version
|
848
851
|
version: '0'
|
849
852
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
850
|
-
none: false
|
851
853
|
requirements:
|
852
|
-
- -
|
854
|
+
- - '>'
|
853
855
|
- !ruby/object:Gem::Version
|
854
|
-
version:
|
856
|
+
version: 1.3.1
|
855
857
|
requirements: []
|
856
858
|
rubyforge_project:
|
857
|
-
rubygems_version:
|
859
|
+
rubygems_version: 2.0.2
|
858
860
|
signing_key:
|
859
|
-
specification_version:
|
861
|
+
specification_version: 4
|
860
862
|
summary: Client for calabash-android for automated functional testing on Android
|
861
863
|
test_files: []
|