acouchi 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|