calabash-android 0.4.6 → 0.4.7.pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|