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 +18 -0
- data/acouchi.gemspec +2 -0
- data/examples/AcouchiSample/AndroidManifest.xml +3 -0
- data/examples/AcouchiSample/Rakefile +1 -0
- data/examples/AcouchiSample/features/step_definitions/steps.rb +20 -0
- data/examples/AcouchiSample/features/support/env.rb +1 -0
- data/examples/AcouchiSample/features/write_text.feature +15 -0
- data/examples/AcouchiSample/res/layout/main.xml +19 -1
- data/examples/AcouchiSample/src/com/acouchi/sample/StartupActivity.java +21 -0
- data/jars/gson-2.2.2.jar +0 -0
- data/lib/acouchi.rb +1 -0
- data/lib/acouchi/apk_modifier.rb +22 -8
- data/lib/acouchi/cucumber.rb +18 -5
- data/lib/acouchi/process_launcher.rb +16 -0
- data/lib/acouchi/project_builder.rb +11 -3
- data/lib/acouchi/rspec/matchers.rb +10 -0
- data/lib/acouchi/solo.rb +59 -3
- data/lib/acouchi/test_runner.rb +13 -8
- data/lib/acouchi/version.rb +1 -1
- data/lib/acouchi/which.rb +22 -0
- data/not-yet-implemented.md +3 -3
- data/src/com/acouchi/Acouchi.java +154 -18
- data/src/com/acouchi/TestCase.java +1 -4
- metadata +37 -2
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.
|
data/acouchi.gemspec
CHANGED
@@ -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">
|
@@ -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
|
@@ -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
|
-
|
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
|
}
|
data/jars/gson-2.2.2.jar
ADDED
Binary file
|
data/lib/acouchi.rb
CHANGED
data/lib/acouchi/apk_modifier.rb
CHANGED
@@ -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
|
-
|
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
|
33
|
-
|
34
|
-
|
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
|
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
|
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
|
-
|
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
|
data/lib/acouchi/cucumber.rb
CHANGED
@@ -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.
|
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
|
-
|
12
|
-
|
13
|
-
|
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(
|
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(
|
87
|
-
ProcessLauncher.new(
|
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
|
data/lib/acouchi/solo.rb
CHANGED
@@ -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
|
-
|
42
|
-
|
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"]
|
data/lib/acouchi/test_runner.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
12
|
-
@test_runner_process.
|
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
|
-
|
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
|
data/lib/acouchi/version.rb
CHANGED
@@ -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
|
data/not-yet-implemented.md
CHANGED
@@ -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
|
-
|
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
|
-
|
76
|
-
|
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
|
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 (
|
100
|
-
|
101
|
-
|
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(
|
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
|
-
|
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.
|
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-
|
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
|