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.
Files changed (20) hide show
  1. checksums.yaml +7 -0
  2. data/calabash-android.gemspec +1 -0
  3. data/doc/calabash-android-help.txt +6 -2
  4. data/lib/calabash-android/lib/TestServer.apk +0 -0
  5. data/lib/calabash-android/operations.rb +86 -14
  6. data/lib/calabash-android/steps/navigation_steps.rb +0 -0
  7. data/lib/calabash-android/version.rb +1 -1
  8. data/test-server/instrumentation-backend/.classpath +1 -0
  9. data/test-server/instrumentation-backend/project.properties +1 -1
  10. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/FranklyResult.java +2 -9
  11. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/HttpServer.java +41 -7
  12. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/ViewDump.java +50 -0
  13. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/softkey/PressMenu.java +0 -0
  14. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/softkey/SelectFromMenuByText.java +0 -0
  15. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/json/JSONUtils.java +18 -0
  16. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/Query.java +1 -2
  17. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/ViewMapper.java +42 -24
  18. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/ast/PartialFutureList.java +2 -2
  19. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/ast/UIQueryUtils.java +222 -9
  20. metadata +37 -35
@@ -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
@@ -21,5 +21,6 @@ Gem::Specification.new do |s|
21
21
  s.add_dependency( "slowhandcuke" )
22
22
  s.add_dependency( "rubyzip" )
23
23
  s.add_dependency( "awesome_print" )
24
+ s.add_dependency( 'httpclient', '~> 2.3.2')
24
25
 
25
26
  end
@@ -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
@@ -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
- http = Net::HTTP.new "127.0.0.1", @server_port
258
- http.open_timeout = options[:open_timeout] if options[:open_timeout]
259
- http.read_timeout = options[:read_timeout] if options[:read_timeout]
260
- resp = http.post(path, "#{data.to_json}", {"Content-Type" => "application/json;charset=utf-8"})
261
- resp.body
262
- rescue EOFError => e
263
- 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."
264
- raise e
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(options, data=nil)
497
- default_device.http(options, data)
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( verb )
624
- ni
695
+ def url_for( method )
696
+ default_device.url_for(method)
625
697
  end
626
698
 
627
- def make_http_request( url, req )
628
- ni
699
+ def make_http_request(options)
700
+ default_device.make_http_request(options)
629
701
  end
630
702
 
631
703
  end
@@ -1,5 +1,5 @@
1
1
  module Calabash
2
2
  module Android
3
- VERSION = "0.4.6"
3
+ VERSION = "0.4.7.pre1"
4
4
  end
5
5
  end
@@ -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>
@@ -8,4 +8,4 @@
8
8
  # project structure.
9
9
 
10
10
  # Project target.
11
- target=Google Inc.:Google APIs:16
11
+ target=Google Inc.:Google APIs:17
@@ -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.org.codehaus.jackson.map.ObjectMapper;
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
- ObjectMapper mapper = new ObjectMapper();
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
- } else if (uri.endsWith("/map")) {
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 and query_all
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();
@@ -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
+ }
@@ -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
+ }
@@ -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>();
@@ -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.getClass().getName());
21
+ data.put("class", getClassNameForView(v));
22
22
  data.put("description", v.toString());
23
- CharSequence description = v.getContentDescription();
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
- rect.put("center_x", location[0] + v.getWidth()/2.0);
44
- rect.put("center_y", location[1] + v.getHeight()/2.0);
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 evaluateWithViews) {
90
- for (Object o : evaluateWithViews) {
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.content.res.Resources.NotFoundException;
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.getWidth() == 0 || view.getWidth() == 0) {
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
- try {
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.6
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-27 00:00:00.000000000 Z
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
- description: ! 'calabash-android drives tests for native and hybrid Android apps. '
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: '0'
856
+ version: 1.3.1
855
857
  requirements: []
856
858
  rubyforge_project:
857
- rubygems_version: 1.8.23
859
+ rubygems_version: 2.0.2
858
860
  signing_key:
859
- specification_version: 3
861
+ specification_version: 4
860
862
  summary: Client for calabash-android for automated functional testing on Android
861
863
  test_files: []