acouchi 0.0.2 → 0.0.3

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.
data/README.md CHANGED
@@ -13,6 +13,8 @@ Requirements
13
13
 
14
14
  * [Android SDK](http://developer.android.com/sdk/installing/index.html) (Make sure to add SDK/tools and SDK/platform-tools to your PATH)
15
15
  * [apktool](http://code.google.com/p/android-apktool/)
16
+ * [ant](http://ant.apache.org/manual/install.html)
17
+ * A running Android emulator, or a plugged in Android device
16
18
 
17
19
  The Build Process
18
20
  -----------------
@@ -38,6 +40,7 @@ Cucumber
38
40
 
39
41
  require "acouchi"
40
42
  require "acouchi/cucumber"
43
+ require "acouchi/rspec/matchers"
41
44
 
42
45
  configuration = Acouchi::Configuration.from_json(File.read("acouchi_configuration.json"))
43
46
  Acouchi::Cucumber.prepare(configuration)
@@ -70,3 +73,18 @@ Cucumber
70
73
  Cucumber::Rake::Task.new(:features) do |t|
71
74
  t.cucumber_opts = "features --format pretty"
72
75
  end
76
+
77
+ Troubleshooting
78
+ ---------------
79
+
80
+ ### Problems clicking on buttons and text
81
+
82
+ If it seems that buttons/text aren't being clicked properly, you need to add the following xml to your AndroidManifest.xml:
83
+
84
+ ```
85
+ <uses-sdk android:targetSdkVersion="SDK_VERSION" />
86
+ ```
87
+
88
+ Where SDK_VERSION is the version of the Android SDK you are using. Version numbers can be found [here](http://developer.android.com/reference/android/os/Build.VERSION_CODES.html)
89
+
90
+ For example, Android 4.0 uses version 14, Android 4.0.3 uses version 15 and Android 4.1 uses version 16.
@@ -18,4 +18,6 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ["lib"]
19
19
  gem.add_runtime_dependency "httparty"
20
20
  gem.add_runtime_dependency "childprocess"
21
+ gem.add_runtime_dependency "which"
22
+ gem.add_runtime_dependency "nokogiri"
21
23
  end
@@ -3,6 +3,9 @@
3
3
  package="com.acouchi.sample"
4
4
  android:versionCode="1"
5
5
  android:versionName="1.0">
6
+
7
+ <uses-sdk android:targetSdkVersion="16" />
8
+
6
9
  <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
7
10
  <activity android:name="StartupActivity"
8
11
  android:label="@string/app_name">
@@ -1,6 +1,7 @@
1
1
  require "cucumber"
2
2
  require "cucumber/rake/task"
3
3
  require "acouchi"
4
+ require "acouchi/rspec/matchers"
4
5
 
5
6
  desc "build project with Acouchi code included"
6
7
  task :build do
@@ -13,3 +13,23 @@ end
13
13
  Then /^I do not see "(.*?)"$/ do |text|
14
14
  page.should_not have_text text
15
15
  end
16
+
17
+ Then /^I see one button$/ do
18
+ page.buttons.size.should == 1
19
+ end
20
+
21
+ Then /^it has the text "(.*?)"$/ do |text|
22
+ page.should have_button text
23
+ end
24
+
25
+ When /^I click the text "(.*?)"$/ do |text|
26
+ page.click_text(text)
27
+ end
28
+
29
+ Then /^I see the content "(.*?)"$/ do |content|
30
+ page.should have_content content
31
+ end
32
+
33
+ Then /^I see all views$/ do
34
+ p page.views
35
+ end
@@ -1,5 +1,6 @@
1
1
  require "acouchi"
2
2
  require "acouchi/cucumber"
3
+ require "acouchi/rspec/matchers"
3
4
 
4
5
  configuration = Acouchi::Configuration.from_json(File.read("acouchi_configuration.json"))
5
6
  Acouchi::Cucumber.prepare(configuration)
@@ -5,3 +5,18 @@ Feature: Write Text
5
5
  Then I see "this is some text"
6
6
  When I clear the text
7
7
  Then I do not see "this is some text"
8
+
9
+ Scenario: Clickety Click
10
+ Then I see one button
11
+ And it has the text "clicky click"
12
+
13
+ Scenario: Click text
14
+ When I click the text "click this text"
15
+ And I click the text "WELL DONE"
16
+ Then I see "WELL DONE"
17
+
18
+ Scenario: Content
19
+ Then I see the content "click this text"
20
+
21
+ Scenario: Views
22
+ Then I see all views
@@ -8,5 +8,23 @@
8
8
  android:layout_width="fill_parent"
9
9
  android:layout_height="wrap_content"
10
10
  />
11
- </LinearLayout>
11
+ <Button
12
+ android:layout_width="fill_parent"
13
+ android:layout_height="wrap_content"
14
+ android:text="clicky click"
15
+ />
16
+
17
+ <TextView
18
+ android:id="@+id/clickThisText"
19
+ android:layout_width="fill_parent"
20
+ android:layout_height="wrap_content"
21
+ android:text="click this text"
22
+ />
12
23
 
24
+ <ImageView
25
+ android:id="@+id/clickThisImageView"
26
+ android:layout_width="fill_parent"
27
+ android:layout_height="wrap_content"
28
+ android:text="click this text"
29
+ />
30
+ </LinearLayout>
@@ -2,6 +2,10 @@ package com.acouchi.sample;
2
2
 
3
3
  import android.app.Activity;
4
4
  import android.os.Bundle;
5
+ import android.widget.TextView;
6
+ import android.view.View.OnClickListener;
7
+ import android.view.View;
8
+ import android.widget.ImageView;
5
9
 
6
10
  public class StartupActivity extends Activity
7
11
  {
@@ -11,5 +15,22 @@ public class StartupActivity extends Activity
11
15
  {
12
16
  super.onCreate(savedInstanceState);
13
17
  setContentView(R.layout.main);
18
+
19
+ TextView clickThis = (TextView)findViewById(R.id.clickThisText);
20
+ clickThis.setOnClickListener(new OnClickListener(){
21
+ public void onClick(View v) {
22
+ TextView clickThis = (TextView)findViewById(R.id.clickThisText);
23
+ clickThis.setText("WELL DONE");
24
+ }
25
+ });
26
+
27
+
28
+ ImageView imageView = (ImageView)findViewById(R.id.clickThisImageView);
29
+ imageView.setOnClickListener(new OnClickListener(){
30
+ public void onClick(View v) {
31
+ TextView clickThis = (TextView)findViewById(R.id.clickThisText);
32
+ clickThis.setText("You click an ImageView");
33
+ }
34
+ });
14
35
  }
15
36
  }
Binary file
@@ -5,3 +5,4 @@ require "acouchi/test_runner"
5
5
  require "acouchi/configuration"
6
6
  require "acouchi/apk_modifier"
7
7
  require "acouchi/process_launcher"
8
+ require "acouchi/which"
@@ -5,10 +5,9 @@ module Acouchi
5
5
  class ApkModifier
6
6
  def initialize apk
7
7
  @apk = apk
8
- @apk_tool = `which apktool`.strip
9
8
  @output_path = "#{Dir.tmpdir}/#{SecureRandom.uuid}/"
10
9
 
11
- if @apk_tool.empty?
10
+ unless apktool
12
11
  puts "Couldn't find a valid apktool. Please install apktool from http://code.google.com/p/android-apktool/"
13
12
  exit
14
13
  end
@@ -29,22 +28,37 @@ module Acouchi
29
28
  end
30
29
 
31
30
  private
32
- def apktool command
33
- puts "#{@apk_tool} #{command}"
34
- system "#{@apk_tool} #{command}"
31
+ def apktool
32
+ @apktool ||= Which.find_executable("apktool.bat", "apktool")
33
+ end
34
+
35
+ def execute_apktool command
36
+ ProcessLauncher.new(apktool, command)
35
37
  end
36
38
 
37
39
  def decompile_apk
38
- apktool "d #{@apk} #{@output_path}"
40
+ ProcessLauncher.new(apktool, "d", @apk, @output_path).start_and_crash_if_process_fails
39
41
  end
40
42
 
41
43
  def compile_apk
42
- apktool "b #{@output_path}"
44
+ ProcessLauncher.new(apktool, "b", @output_path).start_and_crash_if_process_fails
43
45
  end
44
46
 
45
47
  def sign_apk_in_debug_mode
46
48
  @new_apk = File.join(@output_path, "dist", File.basename(@apk))
47
- system "jarsigner -keystore ~/.android/debug.keystore -storepass android -keypass android #{@new_apk} androiddebugkey"
49
+ jarsigner = Which.which?("jarsigner.exe") || Which.which?("jarsigner")
50
+ debug_keystore = File.join(ENV["HOME"], ".android", "debug.keystore")
51
+ ProcessLauncher.new(
52
+ jarsigner,
53
+ "-keystore",
54
+ debug_keystore,
55
+ "-storepass",
56
+ "android",
57
+ "-keypass",
58
+ "android",
59
+ @new_apk,
60
+ "androiddebugkey"
61
+ ).start_and_crash_if_process_fails
48
62
  end
49
63
 
50
64
  def overwrite_original_apk
@@ -1,16 +1,29 @@
1
+ Before do
2
+ Acouchi::Cucumber.before
3
+ end
4
+
5
+ After do
6
+ Acouchi::Cucumber.after
7
+ end
8
+
1
9
  module Acouchi
2
10
  module Cucumber
3
11
  def self.page
4
12
  @page
5
13
  end
6
14
 
7
- def self.prepare configuration
8
- @test_runner = TestRunner.new(configuration)
15
+ def self.before
16
+ @test_runner = TestRunner.new(@configuration)
9
17
  @test_runner.start
10
18
  @page = Solo.new
11
- at_exit do
12
- @test_runner.stop
13
- end
19
+ end
20
+
21
+ def self.after
22
+ @test_runner.stop
23
+ end
24
+
25
+ def self.prepare configuration
26
+ @configuration = configuration
14
27
  end
15
28
  end
16
29
  end
@@ -7,15 +7,31 @@ module Acouchi
7
7
  end
8
8
 
9
9
  def start
10
+ write_out_arguments
10
11
  @process.start
11
12
  @process.wait
12
13
  end
13
14
 
15
+ def start_in_background
16
+ write_out_arguments
17
+ @process.start
18
+ end
19
+
20
+ def stop
21
+ @process.stop
22
+ end
23
+
14
24
  def start_and_crash_if_process_fails
15
25
  start
26
+
16
27
  if @process.crashed?
17
28
  raise "A process exited with a non-zero exit code.\nThe command executed was \"#{@arguments.join(" ")}\""
18
29
  end
19
30
  end
31
+
32
+ private
33
+ def write_out_arguments
34
+ p @arguments
35
+ end
20
36
  end
21
37
  end
@@ -76,15 +76,23 @@ module Acouchi
76
76
  end
77
77
  end
78
78
 
79
+ def adb
80
+ Which.which?("adb.exe") || Which.which?("adb")
81
+ end
82
+
83
+ def ant
84
+ Which.which?("ant.exe") || Which.which?("ant")
85
+ end
86
+
79
87
  def build_apk
80
88
  Dir.chdir configuration.project_path do
81
- ProcessLauncher.new("ant", "clean", "debug").start_and_crash_if_process_fails
89
+ ProcessLauncher.new(ant, "clean", "debug").start_and_crash_if_process_fails
82
90
  end
83
91
  end
84
92
 
85
93
  def install_apk apk_path
86
- ProcessLauncher.new("adb", "uninstall", configuration.target_package).start
87
- ProcessLauncher.new("adb", "install", apk_path).start_and_crash_if_process_fails
94
+ ProcessLauncher.new(adb, "uninstall", configuration.target_package).start
95
+ ProcessLauncher.new(adb, "install", apk_path).start_and_crash_if_process_fails
88
96
  end
89
97
  end
90
98
  end
@@ -0,0 +1,10 @@
1
+ require "rspec"
2
+
3
+ RSpec::Matchers.define :have_content do |expected|
4
+ match do |actual|
5
+ actual.content.include? expected
6
+ end
7
+ failure_message_for_should do |actual|
8
+ "expected \"#{actual.content.join("\n")}\" to include \"#{expected}\""
9
+ end
10
+ end
@@ -35,11 +35,67 @@ module Acouchi
35
35
  ])
36
36
  end
37
37
 
38
+ def buttons
39
+ call_method("getCurrentButtons")
40
+ end
41
+
42
+ def has_button? text
43
+ buttons.any? { |b| b["text"] == text }
44
+ end
45
+
46
+ def content
47
+ call_method("getCurrentContent")
48
+ end
49
+
50
+ def click_text text, options={}
51
+ options = {
52
+ :match => 1,
53
+ :auto_scroll => true
54
+ }.merge(options)
55
+
56
+ call_method("clickOnText", [
57
+ {:type => "java.lang.String", :value => text},
58
+ {:type => "int", :value => options[:match]},
59
+ {:type => "boolean", :value => options[:auto_scroll]}
60
+ ])
61
+ end
62
+
63
+ def views
64
+ call_method("getViews")
65
+ end
66
+
67
+ def click_view id
68
+ call_method("clickOnViewById", [
69
+ {:type => "int", :value => id},
70
+ ])
71
+ end
72
+
73
+ def scroll_up
74
+ call_method("scrollUp")
75
+ end
76
+
77
+ def scroll_down
78
+ call_method("scrollDown")
79
+ end
80
+
81
+ def scroll_up_list index=0
82
+ call_method("scrollUpList", [
83
+ {:type => "int", :value => index}
84
+ ])
85
+ end
86
+
87
+ def scroll_down_list index=0
88
+ call_method("scrollDownList", [
89
+ {:type => "int", :value => index}
90
+ ])
91
+ end
92
+
38
93
  private
39
- def call_method name, arguments
94
+ def call_method name, arguments = []
40
95
  options = { :body => {:parameters => arguments.to_json} }
41
- json = JSON.parse(HTTParty.post("http://127.0.0.1:7103/execute_method/#{name}", options).body)
42
- if json["emptyResult"]
96
+ response = HTTParty.post("http://127.0.0.1:7103/execute_method/#{name}", options)
97
+ json = JSON.parse(response.body, :max_nesting => 100)
98
+ if json.empty?
43
99
  nil
44
100
  else
45
101
  json["result"]
@@ -6,10 +6,15 @@ module Acouchi
6
6
  @configuration = configuration
7
7
  end
8
8
 
9
+ def adb
10
+ @adb ||= Which.find_executable("adb")
11
+ end
12
+
9
13
  def start
10
- system "adb forward tcp:7103 tcp:7103"
11
- @test_runner_process = ChildProcess.build("adb", "shell", "am", "instrument", "-w", "#{@configuration.target_package}/android.test.InstrumentationTestRunner")
12
- @test_runner_process.start
14
+ force_stop
15
+ ProcessLauncher.new(adb, "forward", "tcp:7103", "tcp:7103").start_and_crash_if_process_fails
16
+ @test_runner_process = ProcessLauncher.new(adb, "shell", "am", "instrument", "-w", "#{@configuration.target_package}/android.test.InstrumentationTestRunner")
17
+ @test_runner_process.start_in_background
13
18
 
14
19
  while ready? == false
15
20
  sleep 0.1
@@ -18,16 +23,16 @@ module Acouchi
18
23
 
19
24
  def stop
20
25
  HTTParty.get("http://127.0.0.1:7103/finish") rescue nil
21
- begin
22
- @test_runner_process.poll_for_exit 10
23
- rescue ChildProcess::TimeoutError
24
- @test_runner_process.stop
25
- end
26
+ @test_runner_process.stop
26
27
  end
27
28
 
28
29
  private
29
30
  def ready?
30
31
  HTTParty.get("http://127.0.0.1:7103/").body == "Acouchi" rescue false
31
32
  end
33
+
34
+ def force_stop
35
+ ChildProcess.build(adb, "shell", "am", "force-stop", @configuration.target_package).start.wait
36
+ end
32
37
  end
33
38
  end
@@ -1,3 +1,3 @@
1
1
  module Acouchi
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -0,0 +1,22 @@
1
+ module Acouchi
2
+ class Which
3
+ def self.which? command
4
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
5
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
6
+ exts.each { |ext|
7
+ exe = "#{path}/#{command}#{ext}"
8
+ return exe if File.executable? exe
9
+ }
10
+ end
11
+ return nil
12
+ end
13
+
14
+ def self.find_executable *aliases
15
+ if executable = aliases.find {|a| which? a}
16
+ executable
17
+ else
18
+ raise %{Couldn't find any matches for the aliases "#{aliases.join(", ")}"}
19
+ end
20
+ end
21
+ end
22
+ end
@@ -65,9 +65,9 @@ Methods
65
65
 
66
66
  void clickOnScreen(float x, float y) Clicks on a given coordinate on the screen.
67
67
 
68
- void clickOnText(String text) Clicks on a View displaying a given text.
69
- void clickOnText(String text, int match) Clicks on a View displaying a given text.
70
- void clickOnText(String text, int match, boolean scroll) Clicks on a View displaying a given text.
68
+ * void clickOnText(String text) Clicks on a View displaying a given text.
69
+ * void clickOnText(String text, int match) Clicks on a View displaying a given text.
70
+ * void clickOnText(String text, int match, boolean scroll) Clicks on a View displaying a given text.
71
71
 
72
72
  void clickOnToggleButton(String name) Clicks on a ToggleButton with a given text.
73
73
 
@@ -11,14 +11,22 @@ import java.util.concurrent.locks.ReentrantLock;
11
11
 
12
12
  import android.app.Instrumentation;
13
13
  import android.app.Activity;
14
+ import android.widget.TextView;
15
+ import android.view.View;
14
16
  import java.lang.reflect.Method;
17
+ import java.lang.reflect.InvocationTargetException;
15
18
  import org.json.JSONArray;
16
19
  import org.json.JSONObject;
17
20
  import org.json.JSONException;
18
21
  import java.util.ArrayList;
22
+ import java.util.Map;
23
+ import java.util.HashMap;
24
+ import java.util.Collection;
19
25
  import java.io.PrintWriter;
20
26
  import java.io.StringWriter;
21
27
  import java.io.Writer;
28
+ import com.google.gson.Gson;
29
+ import android.widget.Button;
22
30
 
23
31
  public class Acouchi extends NanoHTTPD
24
32
  {
@@ -64,16 +72,50 @@ public class Acouchi extends NanoHTTPD
64
72
  else if (uri.startsWith("/execute_method"))
65
73
  {
66
74
  String methodName = uri.replace("/execute_method/", "");
67
- return ExecuteMethod(methodName, params.getProperty("parameters"));
75
+ try {
76
+ return ExecuteMethod(methodName, params.getProperty("parameters"));
77
+ } catch (Throwable throwable) {
78
+ return showException(throwable);
79
+ }
68
80
  }
69
81
 
70
82
  return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, "Acouchi");
71
83
  }
72
84
 
73
- private NanoHTTPD.Response ExecuteMethod(String methodName, String json)
85
+ private NanoHTTPD.Response ExecuteMethod(String methodName, String json) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, JSONException, ClassNotFoundException
74
86
  {
75
- try {
76
- JSONArray jsonArray = new JSONArray(json);
87
+ if (methodName.equals("getCurrentContent"))
88
+ return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, getCurrentContentAsJson());
89
+
90
+ JSONArray jsonArray = new JSONArray(json);
91
+
92
+ if (methodName.equals("clickOnText")) {
93
+ String text = jsonArray.getJSONObject(0).getString("value");
94
+ int match = jsonArray.getJSONObject(1).getInt("value");
95
+ boolean scroll = jsonArray.getJSONObject(2).getBoolean("value");
96
+ try {
97
+ solo.clickOnText(text, match, scroll);
98
+ } catch (Exception exception) {
99
+ return showException(exception);
100
+ }
101
+ return displayMethodResultAsJson(methodName, null);
102
+ } else if (methodName.equals("scrollUpList")) {
103
+ int index = jsonArray.getJSONObject(0).getInt("value");
104
+ try {
105
+ solo.scrollUpList(index);
106
+ } catch (Exception exception) {
107
+ return showException(exception);
108
+ }
109
+ return displayMethodResultAsJson(methodName, null);
110
+ } else if (methodName.equals("scrollDownList")) {
111
+ int index = jsonArray.getJSONObject(0).getInt("value");
112
+ try {
113
+ solo.scrollDownList(index);
114
+ } catch (Exception exception) {
115
+ return showException(exception);
116
+ }
117
+ return displayMethodResultAsJson(methodName, null);
118
+ } else {
77
119
  Class[] parameterTypes = new Class[jsonArray.length()];
78
120
  Object[] parameters = new Object[jsonArray.length()];
79
121
 
@@ -83,24 +125,51 @@ public class Acouchi extends NanoHTTPD
83
125
  parameterTypes[i] = getClassType(jsonObject.getString("type"));
84
126
  parameters[i] = getConvertedValue(jsonObject.getString("type"), jsonObject.getString("value"));
85
127
  }
128
+ Object result = executeMethodOnSomeClass(methodName, parameterTypes, parameters);
129
+ return displayMethodResultAsJson(methodName, result);
130
+ }
131
+ }
132
+
133
+ private class MethodExecutor
134
+ {
135
+ private Solo solo;
136
+ private Object[] parameters;
86
137
 
138
+ public MethodExecutor(Solo solo, Object[] parameters)
139
+ {
140
+ this.solo = solo;
141
+ this.parameters = parameters;
142
+ }
143
+ }
144
+
145
+ private Object executeMethodOnSomeClass(String methodName, Class[] parameterTypes, Object[] parameters) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
146
+ {
147
+ try {
87
148
  Method method = solo.getClass().getMethod(methodName, parameterTypes);
88
- return displayMethodResultAsJson(method.invoke(solo, parameters));
149
+ return method.invoke(solo, parameters);
89
150
  } catch (Exception exception) {
90
- return showException(exception);
91
151
  }
152
+
153
+ Method method = this.getClass().getMethod(methodName, parameterTypes);
154
+ return method.invoke(this, parameters);
92
155
  }
93
156
 
94
- private NanoHTTPD.Response displayMethodResultAsJson(Object result)
157
+ private NanoHTTPD.Response displayMethodResultAsJson(String methodName, Object result)
95
158
  {
96
159
  try {
97
160
  JSONObject object = new JSONObject();
98
161
 
99
- if (result == null) {
100
- object.put("emptyResult", true);
101
- } else {
162
+ if (methodName.equals("getCurrentButtons"))
163
+ return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, getButtonsAsJson((ArrayList<Button>)result));
164
+
165
+ if (methodName.equals("getViews"))
166
+ return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, getViewsAsJson((ArrayList<View>)result));
167
+
168
+ if (methodName.equals("getView"))
169
+ return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, getViewAsJson((View)result));
170
+
171
+ if (result != null)
102
172
  object.put("result", result);
103
- }
104
173
 
105
174
  return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, object.toString());
106
175
  } catch (JSONException e) {
@@ -108,6 +177,77 @@ public class Acouchi extends NanoHTTPD
108
177
  }
109
178
  }
110
179
 
180
+ public void clickOnViewById(int id)
181
+ {
182
+ solo.clickOnView(solo.getView(id));
183
+ }
184
+
185
+ private String getViewsAsJson(ArrayList<View> views)
186
+ {
187
+ ArrayList<JsonView> jsonViews = new ArrayList<JsonView>();
188
+ for(int index = 0; index < views.size(); index++)
189
+ jsonViews.add(new JsonView(views.get(index)));
190
+ JsonResult result = new JsonResult(jsonViews);
191
+ return new Gson().toJson(result);
192
+ }
193
+
194
+ private String getViewAsJson(View view)
195
+ {
196
+ JsonResult result = new JsonResult(new JsonView(view));
197
+ return new Gson().toJson(result);
198
+ }
199
+
200
+ private class JsonView
201
+ {
202
+ private int id;
203
+ private String className;
204
+
205
+ public JsonView(View view)
206
+ {
207
+ id = view.getId();
208
+ className = view.getClass().toString();
209
+ }
210
+ }
211
+
212
+ private String getButtonsAsJson(ArrayList<Button> buttons)
213
+ {
214
+ ArrayList<JsonButton> jsonButtons = new ArrayList<JsonButton>();
215
+ for(int index = 0; index < buttons.size(); index++)
216
+ jsonButtons.add(new JsonButton(buttons.get(index)));
217
+ JsonResult result = new JsonResult(jsonButtons);
218
+ return new Gson().toJson(result);
219
+ }
220
+
221
+ private String getCurrentContentAsJson()
222
+ {
223
+ ArrayList<TextView> currentTextViews = solo.getCurrentTextViews(null);
224
+ ArrayList<String> content = new ArrayList<String>();
225
+ for (TextView textView: currentTextViews)
226
+ content.add(textView.getText().toString());
227
+ JsonResult result = new JsonResult(content);
228
+ return new Gson().toJson(result);
229
+ }
230
+
231
+ private class JsonResult
232
+ {
233
+ private Object result;
234
+
235
+ public JsonResult(Object result)
236
+ {
237
+ this.result = result;
238
+ }
239
+ }
240
+
241
+ private class JsonButton
242
+ {
243
+ private String text;
244
+
245
+ public JsonButton(Button button)
246
+ {
247
+ text = button.getText().toString();
248
+ }
249
+ }
250
+
111
251
  private Class getClassType(String name) throws java.lang.ClassNotFoundException
112
252
  {
113
253
  if (name.equals("int")) return int.class;
@@ -130,15 +270,11 @@ public class Acouchi extends NanoHTTPD
130
270
  return value;
131
271
  }
132
272
 
133
- private NanoHTTPD.Response showException(Exception exception)
273
+ private NanoHTTPD.Response showException(Throwable throwable)
134
274
  {
135
- return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, "exception: " + exception.toString() + "\n" + getStackTrace(exception));
136
- }
137
-
138
- private static String getStackTrace(Throwable aThrowable) {
139
275
  final Writer result = new StringWriter();
140
276
  final PrintWriter printWriter = new PrintWriter(result);
141
- aThrowable.printStackTrace(printWriter);
142
- return result.toString();
277
+ throwable.printStackTrace(printWriter);
278
+ return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, "error: " + throwable.getMessage() + "\n" + result.toString());
143
279
  }
144
280
  }
@@ -2,11 +2,9 @@ package com.acouchi;
2
2
 
3
3
  import android.test.ActivityInstrumentationTestCase2;
4
4
  import com.jayway.android.robotium.solo.Solo;
5
-
6
5
  import android.app.Activity;
7
- // import com.example.android.notepad.NotesList;
8
-
9
6
  import com.jayway.android.robotium.solo.Solo;
7
+
10
8
  public class TestCase extends ActivityInstrumentationTestCase2 {
11
9
  private Solo solo;
12
10
  private Acouchi acouchi;
@@ -14,7 +12,6 @@ public class TestCase extends ActivityInstrumentationTestCase2 {
14
12
  public TestCase()
15
13
  {
16
14
  super(ACTIVITY_UNDER_TEST.class);
17
- // super("com.example.android.notepad", NotesList.class);
18
15
  }
19
16
 
20
17
  @Override
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acouchi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-04 00:00:00.000000000 Z
12
+ date: 2012-10-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httparty
@@ -43,6 +43,38 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: which
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: nokogiri
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
46
78
  description: ''
47
79
  email:
48
80
  - andrew.vos@gmail.com
@@ -73,6 +105,7 @@ files:
73
105
  - examples/AcouchiSample/res/layout/main.xml
74
106
  - examples/AcouchiSample/res/values/strings.xml
75
107
  - examples/AcouchiSample/src/com/acouchi/sample/StartupActivity.java
108
+ - jars/gson-2.2.2.jar
76
109
  - jars/robotium-solo-3.4.1.jar
77
110
  - lib/acouchi.rb
78
111
  - lib/acouchi/apk_modifier.rb
@@ -80,9 +113,11 @@ files:
80
113
  - lib/acouchi/cucumber.rb
81
114
  - lib/acouchi/process_launcher.rb
82
115
  - lib/acouchi/project_builder.rb
116
+ - lib/acouchi/rspec/matchers.rb
83
117
  - lib/acouchi/solo.rb
84
118
  - lib/acouchi/test_runner.rb
85
119
  - lib/acouchi/version.rb
120
+ - lib/acouchi/which.rb
86
121
  - not-yet-implemented.md
87
122
  - src/com/acouchi/Acouchi.java
88
123
  - src/com/acouchi/NanoHTTPD.java