honeydew 0.1.4
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/.gitignore +21 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +17 -0
- data/android-server/.gitignore +4 -0
- data/android-server/pom.xml +215 -0
- data/android-server/repo/com/google/android/uiautomator/4.1.1.4/_maven.repositories +4 -0
- data/android-server/repo/com/google/android/uiautomator/4.1.1.4/uiautomator-4.1.1.4.jar +0 -0
- data/android-server/repo/com/google/android/uiautomator/4.1.1.4/uiautomator-4.1.1.4.pom +9 -0
- data/android-server/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/android-maven-plugin-3.5.2-SNAPSHOT.jar +0 -0
- data/android-server/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/android-maven-plugin-3.5.2-SNAPSHOT.pom +56 -0
- data/android-server/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/maven-metadata-local.xml +24 -0
- data/android-server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/_maven.repositories +4 -0
- data/android-server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/commons-jxpath-1.4-SNAPSHOT.jar +0 -0
- data/android-server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/commons-jxpath-1.4-SNAPSHOT.pom +9 -0
- data/android-server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/maven-metadata-local.xml +24 -0
- data/android-server/repo/commons-jxpath/commons-jxpath/maven-metadata-local.xml +11 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/Action.java +65 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/ActionsExecutor.java +73 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/Command.java +16 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/Result.java +36 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/TestRunner.java +26 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/Click.java +20 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/ClickAndWaitForNewWindow.java +20 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/DumpWindowHierarchy.java +28 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/HasSettingsMenuItem.java +23 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/InspectOptionInSettingsMenu.java +32 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsButtonPresent.java +23 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsChildCountEqualTo.java +26 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsElementWithNestedTextPresent.java +24 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsOptionInSettingsMenuDisabled.java +9 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsOptionInSettingsMenuEnabled.java +9 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsTextPresent.java +19 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/LaunchApp.java +25 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/LaunchHome.java +19 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/LongClick.java +20 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/PressBack.java +19 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/PressEnter.java +19 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/SelectFromAppsList.java +24 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/SelectMenuInSettings.java +22 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/SetText.java +27 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/SetTextByIndex.java +25 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/SetTextByLabel.java +28 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/Unlock.java +31 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/WakeUp.java +24 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/httpd/NanoHTTPD.java +1100 -0
- data/android-server/src/main/java/com/uiautomator_cucumber/android_server/httpd/RemoteCommandReceiver.java +64 -0
- data/example/Gemfile +5 -0
- data/example/Rakefile +9 -0
- data/example/features/example.feature +10 -0
- data/example/features/step_definitions/honeydew_steps.rb +1 -0
- data/example/features/support/hooks.rb +4 -0
- data/honeydew.gemspec +28 -0
- data/honeydew.iml +28 -0
- data/lib/honeydew/cucumber.rb +4 -0
- data/lib/honeydew/device.rb +79 -0
- data/lib/honeydew/device_actions.rb +151 -0
- data/lib/honeydew/device_commands.rb +84 -0
- data/lib/honeydew/device_matchers.rb +52 -0
- data/lib/honeydew/dsl.rb +9 -0
- data/lib/honeydew/hooks.rb +0 -0
- data/lib/honeydew/step_definitions.rb +75 -0
- data/lib/honeydew/version.rb +3 -0
- data/lib/honeydew.rb +72 -0
- data/lib/tasks/honeydew.rake +7 -0
- data/spec/honeydew/device_matchers_spec.rb +58 -0
- data/spec/honeydew/device_spec.rb +66 -0
- data/spec/spec_helper.rb +39 -0
- data/todo.md +9 -0
- metadata +232 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package com.uiautomator_cucumber.android_server.httpd;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
import com.google.gson.Gson;
|
|
5
|
+
import com.uiautomator_cucumber.android_server.ActionsExecutor;
|
|
6
|
+
import com.uiautomator_cucumber.android_server.Command;
|
|
7
|
+
import com.uiautomator_cucumber.android_server.Result;
|
|
8
|
+
|
|
9
|
+
import java.io.File;
|
|
10
|
+
import java.io.IOException;
|
|
11
|
+
import java.util.Properties;
|
|
12
|
+
|
|
13
|
+
public class RemoteCommandReceiver extends NanoHTTPD {
|
|
14
|
+
private static final String MIME_JSON = "application/json";
|
|
15
|
+
private static final String TAG = "RemoteCommandReceiver";
|
|
16
|
+
private final Gson gson;
|
|
17
|
+
private final ActionsExecutor actionsExecutor;
|
|
18
|
+
private boolean running = true;
|
|
19
|
+
|
|
20
|
+
public RemoteCommandReceiver(ActionsExecutor actionsExecutor) throws IOException, InterruptedException {
|
|
21
|
+
super(7120, new File("/"));
|
|
22
|
+
Log.d(TAG, "Listening on HTTP port 7120. Kill test to stop");
|
|
23
|
+
this.gson = new Gson();
|
|
24
|
+
this.actionsExecutor = actionsExecutor;
|
|
25
|
+
while (running) {
|
|
26
|
+
Thread.sleep(5000);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Override
|
|
31
|
+
public Response serve(String uri, String method, Properties header, Properties params, Properties files) {
|
|
32
|
+
log(uri, method, params);
|
|
33
|
+
|
|
34
|
+
Result result = tryPerformingControlAction(method, uri);
|
|
35
|
+
if (result == null) {
|
|
36
|
+
result = performActionOnDevice(params);
|
|
37
|
+
}
|
|
38
|
+
return new Response(HTTP_OK, MIME_JSON, gson.toJson(result));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Override
|
|
42
|
+
public void stop() {
|
|
43
|
+
super.stop();
|
|
44
|
+
this.running = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private Result performActionOnDevice(Properties params) {
|
|
48
|
+
return actionsExecutor.execute(gson.fromJson((String) params.get("command"), Command.class));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private Result tryPerformingControlAction(String method, String uri) {
|
|
52
|
+
if (uri.equalsIgnoreCase("/terminate")) {
|
|
53
|
+
stop();
|
|
54
|
+
return Result.OK;
|
|
55
|
+
} else if(method.equalsIgnoreCase("HEAD")) {
|
|
56
|
+
return Result.OK;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private void log(String uri, String method, Properties params) {
|
|
62
|
+
Log.d(TAG, String.format("Processing uri: %s, method: %s, params: %s", uri, method, params));
|
|
63
|
+
}
|
|
64
|
+
}
|
data/example/Gemfile
ADDED
data/example/Rakefile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@honeydew
|
|
2
|
+
Feature: Example demonstrating honeydew
|
|
3
|
+
Scenario: Perform addition using calculator
|
|
4
|
+
When I launch app "Calculator"
|
|
5
|
+
Then I long press the element with description "square root"
|
|
6
|
+
Then I press the "6" button
|
|
7
|
+
Then I press the "2" button
|
|
8
|
+
Then I press the "5" button
|
|
9
|
+
And I press the "=" button
|
|
10
|
+
Then I wait up to 2 seconds for "25" to appear in edit text
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'honeydew/step_definitions'
|
data/honeydew.gemspec
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'honeydew/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "honeydew"
|
|
8
|
+
spec.version = Honeydew::VERSION
|
|
9
|
+
spec.authors = ["Selva", "Christopher Rex"]
|
|
10
|
+
spec.email = ["scmp-team@amplify.com"]
|
|
11
|
+
spec.description = %q{Automated functional testing on Android with uiautomator and cucumber}
|
|
12
|
+
spec.summary = %q{Automated functional testing on Android with uiautomator and cucumber}
|
|
13
|
+
spec.homepage = ""
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files`.split($/) + Dir['android-server/target/*.jar']
|
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
|
21
|
+
spec.add_development_dependency "rake"
|
|
22
|
+
spec.add_development_dependency "rspec"
|
|
23
|
+
|
|
24
|
+
spec.add_dependency("cucumber")
|
|
25
|
+
spec.add_dependency("json")
|
|
26
|
+
spec.add_dependency("rest-client")
|
|
27
|
+
spec.add_dependency("activesupport")
|
|
28
|
+
end
|
data/honeydew.iml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="RUBY_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
4
|
+
<exclude-output />
|
|
5
|
+
<content url="file://$MODULE_DIR$">
|
|
6
|
+
<sourceFolder url="file://$MODULE_DIR$/android-server/src" isTestSource="false" />
|
|
7
|
+
</content>
|
|
8
|
+
<orderEntry type="jdk" jdkName="RVM: jruby-1.7.3" jdkType="JRUBY_SDK" />
|
|
9
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
10
|
+
<orderEntry type="library" scope="PROVIDED" name="activesupport (v3.2.13, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
11
|
+
<orderEntry type="library" scope="PROVIDED" name="builder (v3.2.0, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
12
|
+
<orderEntry type="library" scope="PROVIDED" name="bundler (v1.3.5, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
13
|
+
<orderEntry type="library" scope="PROVIDED" name="cucumber (v1.3.1, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
14
|
+
<orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.2.4, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
15
|
+
<orderEntry type="library" scope="PROVIDED" name="gherkin (v2.12.0, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
16
|
+
<orderEntry type="library" scope="PROVIDED" name="i18n (v0.6.1, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
17
|
+
<orderEntry type="library" scope="PROVIDED" name="json (v1.7.7, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
18
|
+
<orderEntry type="library" scope="PROVIDED" name="mime-types (v1.23, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
19
|
+
<orderEntry type="library" scope="PROVIDED" name="multi_json (v1.7.3, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
20
|
+
<orderEntry type="library" scope="PROVIDED" name="rake (v10.0.4, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
21
|
+
<orderEntry type="library" scope="PROVIDED" name="rest-client (v1.6.7, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
22
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec (v2.13.0, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
23
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-core (v2.13.1, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
24
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v2.13.0, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
25
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v2.13.1, RVM: jruby-1.7.3) [gem]" level="application" />
|
|
26
|
+
</component>
|
|
27
|
+
</module>
|
|
28
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require "honeydew/device_matchers"
|
|
2
|
+
require "honeydew/device_actions"
|
|
3
|
+
|
|
4
|
+
module Honeydew
|
|
5
|
+
class Device
|
|
6
|
+
include Honeydew::DeviceActions
|
|
7
|
+
include Honeydew::DeviceMatchers
|
|
8
|
+
|
|
9
|
+
attr_reader :serial, :port
|
|
10
|
+
|
|
11
|
+
def initialize(serial)
|
|
12
|
+
if serial.to_s.empty?
|
|
13
|
+
raise ArgumentError, 'HoneyDew: Invalid serial or no device connected'
|
|
14
|
+
end
|
|
15
|
+
@port = Honeydew.config.obtain_new_port
|
|
16
|
+
@serial = serial
|
|
17
|
+
start_uiautomator_server
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def contains_textview_text?(text, timeout = Honeydew.config.timeout)
|
|
21
|
+
response = has_textview_text? text, timeout
|
|
22
|
+
response['success']
|
|
23
|
+
rescue
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def contains_element_with_description?(description, timeout = Honeydew.config.timeout)
|
|
28
|
+
response = has_element_with_description? description, timeout
|
|
29
|
+
response['success']
|
|
30
|
+
rescue
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def contains_button?(text, timeout = Honeydew.config.timeout)
|
|
35
|
+
response = has_button? text, timeout
|
|
36
|
+
response['success']
|
|
37
|
+
rescue
|
|
38
|
+
false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def is_app_installed?(package_name)
|
|
42
|
+
has_app_installed?(package_name)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def clear_directory(directory)
|
|
46
|
+
all_files_in_directory_path = [directory.chomp('/'), '/*'].join
|
|
47
|
+
adb "shell rm -r #{all_files_in_directory_path}"
|
|
48
|
+
end
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def ensure_tablet_ready
|
|
52
|
+
@device_ready ||= begin
|
|
53
|
+
wait_for_android_server
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def wait_for_android_server
|
|
59
|
+
log 'Waiting for server to come up'
|
|
60
|
+
retriable :on => [RestClient::ServerBrokeConnection, Errno::ECONNRESET, Errno::ECONNREFUSED], :interval => 0.3, :tries => 30 do
|
|
61
|
+
RestClient.head device_endpoint
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def retriable(options, &block)
|
|
66
|
+
tries = options[:tries]
|
|
67
|
+
yield
|
|
68
|
+
rescue *[*options[:on]]
|
|
69
|
+
tries -= 1
|
|
70
|
+
if tries > 0
|
|
71
|
+
sleep options[:interval]
|
|
72
|
+
retry
|
|
73
|
+
else
|
|
74
|
+
raise
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
require "honeydew/device_commands"
|
|
2
|
+
|
|
3
|
+
module Honeydew
|
|
4
|
+
module DeviceActions
|
|
5
|
+
include Honeydew::DeviceCommands
|
|
6
|
+
|
|
7
|
+
def click_button(button_text)
|
|
8
|
+
perform_action :action => 'click', :arguments => {:text => button_text, :type => 'Button'}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def click_button_and_wait(button_text)
|
|
12
|
+
perform_action :action => 'click_and_wait_for_new_window', :arguments => {:text => button_text, :type => 'Button'}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def click_text(text)
|
|
16
|
+
perform_action :action => 'click', :arguments => {:text => text, :type => 'TextView'}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def click_text_and_wait(text)
|
|
20
|
+
perform_action :action => 'click_and_wait_for_new_window', :arguments => {:text => text, :type => 'TextView'}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def click_element(element_description)
|
|
24
|
+
perform_action :action => 'click', :arguments => {:description => element_description}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def click_element_and_wait(element_description)
|
|
28
|
+
perform_action :action => 'click_and_wait_for_new_window', :arguments => {:description => element_description}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fill_in field_description, options = {with: ''}
|
|
32
|
+
perform_action :action => 'set_text', :arguments => {:description => field_description, :text => options[:with]}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def fill_in_by_label field_label, options = {with: ''}
|
|
36
|
+
perform_action :action => 'set_text_by_label', :arguments => {:label => field_label, :text => options[:with]}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def fill_in_by_index index, options = {with: ''}
|
|
40
|
+
perform_action :action => 'set_text_by_index', :arguments => {:index => index, :text => options[:with]}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def launch_home
|
|
44
|
+
perform_action :action => 'launch_home'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def launch_settings_app(app_name)
|
|
48
|
+
perform_action :action => 'select_from_apps_list', :arguments => {:appName => app_name}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def launch_application(application_name)
|
|
52
|
+
perform_action :action => 'launch_app', :arguments => {:appName => application_name}, :attempts => 3
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def long_click_element(element_description)
|
|
56
|
+
perform_action :action => 'long_click', :arguments => {:description => element_description}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def launch_settings_item(item_name)
|
|
60
|
+
perform_action :action => 'select_menu_in_settings', :arguments => {:menuName => item_name}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def press_back
|
|
65
|
+
perform_action :action => 'press_back'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def press_enter
|
|
69
|
+
perform_action :action => 'press_enter'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def unlock
|
|
73
|
+
perform_action :action => 'unlock'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def wake_up
|
|
77
|
+
perform_action :action => 'wake_up'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def perform_action(options)
|
|
81
|
+
ensure_tablet_ready
|
|
82
|
+
|
|
83
|
+
command = options.slice(:action, :arguments)
|
|
84
|
+
timeout = options[:retry_until]
|
|
85
|
+
attempts = options[:attempts]
|
|
86
|
+
|
|
87
|
+
response =
|
|
88
|
+
if timeout
|
|
89
|
+
retry_until_timeout(timeout, command)
|
|
90
|
+
elsif attempts
|
|
91
|
+
retry_until_success(attempts, command)
|
|
92
|
+
else
|
|
93
|
+
execute_command(command)
|
|
94
|
+
end
|
|
95
|
+
log_action(command, response)
|
|
96
|
+
raise "Device: #{serial} :Action #{options} failed." unless response['success']
|
|
97
|
+
response
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def retry_until_success(attempts, command)
|
|
103
|
+
completed = false
|
|
104
|
+
response = nil
|
|
105
|
+
tries = 0
|
|
106
|
+
until completed || (tries >= attempts) do
|
|
107
|
+
response = execute_command(command)
|
|
108
|
+
completed = response['success']
|
|
109
|
+
return response if completed
|
|
110
|
+
tries += 1
|
|
111
|
+
sleep 1
|
|
112
|
+
end
|
|
113
|
+
log_action(command, response)
|
|
114
|
+
raise "Device: #{serial} : All #{attempts} attempts failed while performing #{command[:action]}, with arguments: #{command[:arguments]}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def retry_until_timeout(timeout, command)
|
|
118
|
+
completed = false
|
|
119
|
+
response = nil
|
|
120
|
+
Timeout.timeout(timeout.to_i) do
|
|
121
|
+
until completed do
|
|
122
|
+
sleep 1
|
|
123
|
+
response = execute_command(command)
|
|
124
|
+
completed = response['success']
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
return response
|
|
128
|
+
rescue Timeout::Error
|
|
129
|
+
log_action(command, response)
|
|
130
|
+
raise "Device: #{serial} :Timeout while performing #{command[:action]}, with arguments: #{command[:arguments]}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def execute_command(command)
|
|
134
|
+
response = RestClient.get(device_endpoint, :params => stringify_keys(:command => command))
|
|
135
|
+
JSON.parse(response)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def log_action(command, response)
|
|
139
|
+
log "Device: #{serial} : executing command: #{command}"
|
|
140
|
+
log "Device: #{serial} : responded with: #{response}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def stringify_keys(options)
|
|
144
|
+
JSON.parse(options.to_json)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def log(message)
|
|
148
|
+
puts message
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Honeydew
|
|
2
|
+
module DeviceCommands
|
|
3
|
+
|
|
4
|
+
def device_endpoint
|
|
5
|
+
"http://127.0.0.1:#{port}"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def dump_window_hierarchy(local_path)
|
|
9
|
+
path_in_device = perform_action(:action => 'dump_window_hierarchy')['description']
|
|
10
|
+
adb "pull #{path_in_device} #{local_path}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def take_screenshot(local_path)
|
|
14
|
+
path_in_device = '/data/local/tmp/honeydew.png'
|
|
15
|
+
adb "shell /system/bin/screencap -p #{path_in_device}"
|
|
16
|
+
adb "pull #{path_in_device} #{local_path}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def start_uiautomator_server
|
|
20
|
+
forwarding_port
|
|
21
|
+
terminate_uiautomator_server
|
|
22
|
+
log "Device: #{serial} : Starting server on the device"
|
|
23
|
+
start_automation_server
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def terminate_uiautomator_server
|
|
27
|
+
log 'Terminating server'
|
|
28
|
+
RestClient.get("#{device_endpoint}/terminate")
|
|
29
|
+
rescue
|
|
30
|
+
# Swallow
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def automation_test_jar_name
|
|
34
|
+
"android-server-0.0.1-SNAPSHOT.jar"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def automation_server_jar_path
|
|
38
|
+
File.absolute_path(File.join(File.dirname(__FILE__), "../../android-server/target/#{automation_test_jar_name}"))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def start_automation_server
|
|
42
|
+
Thread.new do
|
|
43
|
+
adb "push #{automation_server_jar_path} /data/local/tmp"
|
|
44
|
+
adb "shell uiautomator runtest #{automation_test_jar_name} -c com.uiautomator_cucumber.android_server.TestRunner"
|
|
45
|
+
log "Device: #{serial} initiated the start of automation server"
|
|
46
|
+
end
|
|
47
|
+
at_exit do
|
|
48
|
+
terminate_uiautomator_server
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def forwarding_port
|
|
53
|
+
log "Device: #{serial} : Forwarding port #{port} to device"
|
|
54
|
+
adb "forward tcp:#{port} tcp:7120"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def uninstall_app(package_name)
|
|
58
|
+
adb "uninstall #{package_name}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def install_app(apk_location)
|
|
62
|
+
adb "install #{apk_location}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def clear_app_data(package_name)
|
|
66
|
+
adb "shell pm clear #{package_name}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def reboot
|
|
70
|
+
adb 'reboot'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def launch_settings
|
|
74
|
+
adb 'shell am start -n com.android.settings/com.android.settings.Settings'
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def adb(command)
|
|
78
|
+
adb_command = "adb -s #{serial} #{command}"
|
|
79
|
+
log "Device: #{serial} :Executing '#{adb_command}'"
|
|
80
|
+
`#{adb_command}`.tap { raise 'ADB command failed' unless $?.success? }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Honeydew
|
|
2
|
+
module DeviceMatchers
|
|
3
|
+
|
|
4
|
+
def has_text?(text, timeout = Honeydew.config.timeout)
|
|
5
|
+
perform_action :action => 'is_text_present', :arguments => {:text => text}, :retry_until => timeout
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def has_element_with_description?(description, timeout = Honeydew.config.timeout)
|
|
9
|
+
perform_action :action => 'is_text_present', :arguments => {:description => description}, :retry_until => timeout
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def has_edit_text?(text, timeout = Honeydew.config.timeout)
|
|
13
|
+
perform_action :action => 'is_text_present', :arguments => {:text => text, :type => 'EditText'}, :retry_until => timeout
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def has_textview_text?(text, timeout = Honeydew.config.timeout)
|
|
17
|
+
perform_action :action => 'is_text_present', :arguments => {:text => text, :type => 'TextView'}, :retry_until => timeout
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def has_textview_with_text_and_description?(text, description, timeout=Honeydew.config.timeout)
|
|
21
|
+
perform_action :action => 'is_text_present', :arguments => {:text => text, :description => description, :type => 'TextView'}, :retry_until => timeout
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def has_button?(button_text, timeout = Honeydew.config.timeout)
|
|
25
|
+
perform_action(:action => 'is_button_present', :arguments => {:text => button_text}, :retry_until => timeout)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def has_child_count?(parent_element_description, child_element_description, child_count, timeout = Honeydew.config.timeout)
|
|
29
|
+
perform_action(:action => 'is_child_count_equal_to', :arguments => {:parent_description => parent_element_description, :child_description => child_element_description, :child_count => child_count}, :retry_until => timeout)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def has_element_with_nested_text?(parent_description, child_text, timeout = Honeydew.config.timeout)
|
|
33
|
+
perform_action(:action => 'is_element_with_nested_text_present', :arguments => {:parent_description => parent_description, :child_text => child_text}, :retry_until => timeout)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def has_app_installed?(package_name)
|
|
37
|
+
adb('shell pm list packages').include?(package_name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def is_option_in_setting_enabled?(item_name, option_names)
|
|
41
|
+
perform_action :action => 'is_option_in_settings_menu_enabled', :arguments => {:menuName => item_name, :optionNames => option_names}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def is_option_in_setting_disabled?(item_name, option_names)
|
|
45
|
+
perform_action :action => 'is_option_in_settings_menu_disabled', :arguments => {:menuName => item_name, :optionNames => option_names}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def has_settings_menu_item?(item_name, timeout = 10)
|
|
49
|
+
perform_action(:action => 'has_settings_menu_item', :arguments => {:menuName => item_name}, :retry_until => timeout)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/honeydew/dsl.rb
ADDED
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Given /^I unlock$/ do
|
|
2
|
+
Honeydew.current_device.unlock
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
Given /^I launch app "(.*?)"$/ do |app_name|
|
|
6
|
+
Honeydew.current_device.launch_application app_name
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Given /^I launch home$/ do
|
|
10
|
+
Honeydew.current_device.launch_home
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
When /^I press back$/ do
|
|
14
|
+
Honeydew.current_device.press_back
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Given /^I select "(.*?)" in the Settings view$/ do |menu_item|
|
|
18
|
+
Honeydew.current_device.launch_settings_item menu_item
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Given /^I select app "(.*?)" from the Apps list in the Settings view$/ do |app_name|
|
|
22
|
+
Honeydew.current_device.launch_settings_app app_name
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Given /^I should see text containing "(.*?)"$/ do |expected_text|
|
|
26
|
+
Honeydew.current_device.should have_textview_text expected_text
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Given /^I enter text "(.*?)" into field with description "(.*?)"$/ do |text, field_description|
|
|
30
|
+
Honeydew.current_device.fill_in field_description, with: text
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Given /^I press the "(.*?)" button$/ do |button_text|
|
|
34
|
+
Honeydew.current_device.click_button button_text
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Given /^I press the "(.*?)" text$/ do |text|
|
|
38
|
+
Honeydew.current_device.click_text text
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
Given /^I press the element with description "(.*?)"$/ do |element_description|
|
|
42
|
+
Honeydew.current_device.click_element element_description
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
Given /^I long press the element with description "(.*?)"$/ do |element_description|
|
|
46
|
+
Honeydew.current_device.long_click_element element_description
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
When /^I wait up to (\d+) seconds for "(.*?)" to appear$/ do |timeout, text|
|
|
50
|
+
Honeydew.current_device.should have_textview_text text, timeout
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
When /^I wait up to (\d+) seconds for "(.*?)" to appear in edit text$/ do |timeout, text|
|
|
54
|
+
Honeydew.current_device.should have_edit_text text, timeout
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
Given /I uninstall the app "(.*?)" using ADB$/ do |pacakge_name|
|
|
58
|
+
Honeydew.current_device.uninstall_app pacakge_name
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Given /I install the app "(.*?)" using ADB$/ do |apk_location|
|
|
62
|
+
Honeydew.current_device.install_app apk_location
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Given /I reboot the device using ADB$/ do
|
|
66
|
+
Honeydew.current_device.reboot
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
Then /I see the app "(.*?)" installed using ADB$/ do |package_name|
|
|
70
|
+
Honeydew.current_device.should have_app_installed package_name
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Then /I should see a button "(.*?)" with (package|description) as "(.*?)"$/ do |button_text, condition, package_name|
|
|
74
|
+
Honeydew.current_device.should have_button button_text
|
|
75
|
+
end
|