honeydew 0.20.0 → 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/honeydew/device_matchers.rb +8 -0
- data/lib/honeydew/version.rb +1 -1
- data/server/pom.xml +7 -6
- data/server/src/main/java/com/amplify/honeydew_server/Action.java +6 -4
- data/server/src/main/java/com/amplify/honeydew_server/ActionsExecutor.java +14 -18
- data/server/src/main/java/com/amplify/honeydew_server/TestRunner.java +5 -4
- data/server/src/main/java/com/amplify/honeydew_server/actions/AbstractChildCountAction.java +33 -0
- data/server/src/main/java/com/amplify/honeydew_server/actions/IsChildCountEqualTo.java +5 -13
- data/server/src/main/java/com/amplify/honeydew_server/actions/IsChildCountGreaterThan.java +16 -0
- data/server/src/main/java/com/amplify/honeydew_server/httpd/RemoteCommandReceiver.java +34 -19
- data/server/target/honeydew-server-0.21.0.jar +0 -0
- metadata +5 -3
- data/server/target/honeydew-server-0.20.0.jar +0 -0
@@ -24,6 +24,7 @@ module Honeydew
|
|
24
24
|
perform_assertion :is_button_present, :text => button_text
|
25
25
|
end
|
26
26
|
|
27
|
+
# TODO: Look for a better way to express these similar looking matchers
|
27
28
|
def has_child_count?(parent_element_description, child_element_description, child_count)
|
28
29
|
perform_assertion :is_child_count_equal_to,
|
29
30
|
:parent_description => parent_element_description,
|
@@ -31,6 +32,13 @@ module Honeydew
|
|
31
32
|
:child_count => child_count
|
32
33
|
end
|
33
34
|
|
35
|
+
def has_child_count_greater_than?(parent_element_description, child_element_description, child_count)
|
36
|
+
perform_assertion :is_child_count_greater_than,
|
37
|
+
:parent_description => parent_element_description,
|
38
|
+
:child_description => child_element_description,
|
39
|
+
:child_count => child_count
|
40
|
+
end
|
41
|
+
|
34
42
|
def has_element_with_nested_text?(parent_description, child_text)
|
35
43
|
perform_assertion :is_element_with_nested_text_present,
|
36
44
|
:parent_description => parent_description,
|
data/lib/honeydew/version.rb
CHANGED
data/server/pom.xml
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
<groupId>amplify</groupId>
|
7
7
|
<artifactId>honeydew-server</artifactId>
|
8
|
-
<version>0.
|
8
|
+
<version>0.21.0</version>
|
9
9
|
<packaging>jar</packaging>
|
10
10
|
<name>Honeydew Android Server</name>
|
11
11
|
|
@@ -72,11 +72,12 @@
|
|
72
72
|
<groupId>fi.iki.elonen</groupId>
|
73
73
|
<artifactId>nanohttpd</artifactId>
|
74
74
|
<version>2.0.3</version>
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
75
|
+
<exclusions>
|
76
|
+
<exclusion>
|
77
|
+
<groupId>org.apache.httpcomponents</groupId>
|
78
|
+
<artifactId>httpclient</artifactId>
|
79
|
+
</exclusion>
|
80
|
+
</exclusions>
|
80
81
|
</dependency>
|
81
82
|
</dependencies>
|
82
83
|
|
@@ -9,6 +9,8 @@ import java.util.Map;
|
|
9
9
|
public abstract class Action {
|
10
10
|
protected final UiDevice uiDevice;
|
11
11
|
|
12
|
+
protected final String TAG = getClass().getSimpleName();
|
13
|
+
|
12
14
|
public Action(UiDevice uiDevice) {
|
13
15
|
this.uiDevice = uiDevice;
|
14
16
|
}
|
@@ -39,17 +41,17 @@ public abstract class Action {
|
|
39
41
|
protected ViewSelector getViewSelector(Map<String, Object> arguments) {
|
40
42
|
ViewSelector viewSelector = new ViewSelector();
|
41
43
|
|
42
|
-
Log.d(
|
44
|
+
Log.d(TAG, arguments.toString());
|
43
45
|
if (arguments.containsKey("type")) {
|
44
|
-
Log.d(
|
46
|
+
Log.d(TAG, String.format("Type %s", (String) arguments.get("type")));
|
45
47
|
viewSelector.withType((String) arguments.get("type"));
|
46
48
|
}
|
47
49
|
if (arguments.containsKey("text")) {
|
48
|
-
Log.d(
|
50
|
+
Log.d(TAG, String.format("Text %s", (String) arguments.get("text")));
|
49
51
|
viewSelector.withText((String) arguments.get("text"));
|
50
52
|
}
|
51
53
|
if (arguments.containsKey("description")) {
|
52
|
-
Log.d(
|
54
|
+
Log.d(TAG, String.format("Description %s", (String) arguments.get("description")));
|
53
55
|
viewSelector.withDescription((String) arguments.get("description"));
|
54
56
|
}
|
55
57
|
return viewSelector;
|
@@ -8,24 +8,26 @@ import com.google.common.base.Stopwatch;
|
|
8
8
|
|
9
9
|
import java.lang.reflect.Constructor;
|
10
10
|
import java.lang.reflect.InvocationTargetException;
|
11
|
-
import java.util.
|
11
|
+
import java.util.Map;
|
12
12
|
import java.util.Set;
|
13
|
-
import java.util.concurrent.TimeUnit;
|
14
13
|
|
14
|
+
import static com.google.common.collect.Maps.newHashMap;
|
15
15
|
import static com.google.common.collect.Sets.newHashSet;
|
16
|
+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
16
17
|
|
17
18
|
public class ActionsExecutor {
|
18
19
|
|
19
|
-
|
20
|
-
private final
|
20
|
+
protected final UiDevice uiDevice;
|
21
|
+
private final Map<String, Action> actions;
|
22
|
+
private static final String TAG = ActionsExecutor.class.getName();
|
21
23
|
|
22
24
|
public ActionsExecutor(UiDevice uiDevice) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
|
23
25
|
this.uiDevice = uiDevice;
|
24
|
-
actions =
|
26
|
+
actions = newHashMap();
|
25
27
|
for (Class<? extends Action> actionClass : allActionClasses()) {
|
26
28
|
Constructor<? extends Action> constructor = actionClass.getConstructor(UiDevice.class);
|
27
|
-
Action action = constructor.newInstance(
|
28
|
-
Log.d(
|
29
|
+
Action action = constructor.newInstance(this.uiDevice);
|
30
|
+
Log.d(TAG, String.format("Registering action: %s", action.name()));
|
29
31
|
actions.put(action.name(), action);
|
30
32
|
}
|
31
33
|
}
|
@@ -35,12 +37,9 @@ public class ActionsExecutor {
|
|
35
37
|
try {
|
36
38
|
Action action = actions.get(actionName);
|
37
39
|
if (action == null) {
|
38
|
-
return new Result("Action: " + actionName + " does not
|
40
|
+
return new Result("Action: " + actionName + " does not exist");
|
39
41
|
}
|
40
|
-
|
41
|
-
Result result = executeWithStopwatch(command, action);
|
42
|
-
|
43
|
-
return result;
|
42
|
+
return executeWithStopwatch(command, action);
|
44
43
|
} catch (Exception e) {
|
45
44
|
return new Result("Exception, on calling " + actionName, e);
|
46
45
|
}
|
@@ -52,8 +51,8 @@ public class ActionsExecutor {
|
|
52
51
|
Result result = action.execute(command.getArguments());
|
53
52
|
|
54
53
|
timer.stop();
|
55
|
-
Log.i(
|
56
|
-
command.getAction(), timer.elapsed(
|
54
|
+
Log.i(TAG, String.format("action '%s' took %d ms to execute on the tablet",
|
55
|
+
command.getAction(), timer.elapsed(MILLISECONDS)));
|
57
56
|
return result;
|
58
57
|
}
|
59
58
|
|
@@ -68,6 +67,7 @@ public class ActionsExecutor {
|
|
68
67
|
actionClasses.add(IsButtonPresent.class);
|
69
68
|
actionClasses.add(IsElementWithNestedTextPresent.class);
|
70
69
|
actionClasses.add(IsChildCountEqualTo.class);
|
70
|
+
actionClasses.add(IsChildCountGreaterThan.class);
|
71
71
|
actionClasses.add(Click.class);
|
72
72
|
actionClasses.add(ClickAndWaitForNewWindow.class);
|
73
73
|
actionClasses.add(LongClick.class);
|
@@ -84,8 +84,4 @@ public class ActionsExecutor {
|
|
84
84
|
actionClasses.add(ScrollToTextByIndex.class);
|
85
85
|
return actionClasses;
|
86
86
|
}
|
87
|
-
|
88
|
-
private UiDevice getUiDevice() {
|
89
|
-
return uiDevice;
|
90
|
-
}
|
91
87
|
}
|
@@ -5,21 +5,22 @@ import android.view.KeyEvent;
|
|
5
5
|
import com.amplify.honeydew_server.httpd.RemoteCommandReceiver;
|
6
6
|
import com.android.uiautomator.core.UiDevice;
|
7
7
|
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
|
8
|
-
import fi.iki.elonen.ServerRunner;
|
9
8
|
|
10
9
|
public class TestRunner extends UiAutomatorTestCase {
|
11
10
|
|
11
|
+
private static final String TAG = TestRunner.class.getSimpleName();
|
12
|
+
|
12
13
|
public void testRemoteLoop() throws Exception {
|
13
|
-
Log.d(
|
14
|
+
Log.d(TAG, "Starting honeydew-server...");
|
14
15
|
|
15
16
|
UiDevice uiDevice = getUiDevice();
|
16
17
|
uiDevice.wakeUp();
|
17
18
|
unlockEmulator();
|
18
19
|
|
19
20
|
RemoteCommandReceiver remoteCommandReceiver = new RemoteCommandReceiver(new ActionsExecutor(uiDevice));
|
20
|
-
|
21
|
+
remoteCommandReceiver.start();
|
21
22
|
|
22
|
-
Log.d(
|
23
|
+
Log.d(TAG, "honeydew-server started, waiting for commands");
|
23
24
|
while(true) {
|
24
25
|
Thread.sleep(1000);
|
25
26
|
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
package com.amplify.honeydew_server.actions;
|
2
|
+
|
3
|
+
import android.util.Log;
|
4
|
+
import com.amplify.honeydew_server.Action;
|
5
|
+
import com.amplify.honeydew_server.Result;
|
6
|
+
import com.android.uiautomator.core.UiCollection;
|
7
|
+
import com.android.uiautomator.core.UiDevice;
|
8
|
+
import com.android.uiautomator.core.UiObjectNotFoundException;
|
9
|
+
import com.android.uiautomator.core.UiSelector;
|
10
|
+
|
11
|
+
import java.util.Map;
|
12
|
+
|
13
|
+
public abstract class AbstractChildCountAction extends Action {
|
14
|
+
public AbstractChildCountAction(UiDevice uiDevice) {
|
15
|
+
super(uiDevice);
|
16
|
+
}
|
17
|
+
|
18
|
+
protected abstract boolean isTrue(int childCount, int count);
|
19
|
+
|
20
|
+
@Override
|
21
|
+
public final Result execute(Map<String, Object> arguments) throws UiObjectNotFoundException {
|
22
|
+
Log.d(TAG, "Entering " + TAG);
|
23
|
+
String parentDescription = (String) arguments.get("parent_description");
|
24
|
+
String childDescription = (String) arguments.get("child_description");
|
25
|
+
int expectedCount = ((Double) arguments.get("child_count")).intValue();
|
26
|
+
|
27
|
+
UiCollection parentElement = new UiCollection(new UiSelector().description(parentDescription));
|
28
|
+
int actualCount = parentElement.getChildCount(new UiSelector().description(childDescription));
|
29
|
+
Log.d(TAG, "Actual count was: " + actualCount + " when " + expectedCount + " was expected");
|
30
|
+
return new Result(isTrue(expectedCount, actualCount), "Actual count was: " + actualCount + " when " + expectedCount + " was expected");
|
31
|
+
|
32
|
+
}
|
33
|
+
}
|
@@ -1,25 +1,17 @@
|
|
1
1
|
package com.amplify.honeydew_server.actions;
|
2
2
|
|
3
|
-
import com.
|
4
|
-
import com.android.uiautomator.core.*;
|
3
|
+
import com.android.uiautomator.core.UiDevice;
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
public class IsChildCountEqualTo extends Action {
|
5
|
+
public class IsChildCountEqualTo extends AbstractChildCountAction {
|
9
6
|
|
10
7
|
public IsChildCountEqualTo(UiDevice uiDevice) {
|
11
8
|
super(uiDevice);
|
12
9
|
}
|
13
10
|
|
14
11
|
@Override
|
15
|
-
|
16
|
-
|
17
|
-
String childDescription = (String) arguments.get("child_description");
|
18
|
-
int childCount = (Integer) arguments.get("child_count");
|
19
|
-
|
20
|
-
UiCollection parentElement = new UiCollection(new UiSelector().description(parentDescription));
|
21
|
-
int count = parentElement.getChildCount(new UiSelector().description(childDescription));
|
22
|
-
return new Result(childCount == count, "Actual count was: " + count + " when " + childCount + " was expected");
|
12
|
+
protected boolean isTrue(int childCount, int count) {
|
13
|
+
return childCount == count;
|
23
14
|
}
|
24
15
|
|
16
|
+
|
25
17
|
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
package com.amplify.honeydew_server.actions;
|
2
|
+
|
3
|
+
import com.android.uiautomator.core.UiDevice;
|
4
|
+
|
5
|
+
public class IsChildCountGreaterThan extends AbstractChildCountAction {
|
6
|
+
|
7
|
+
public IsChildCountGreaterThan(UiDevice uiDevice) {
|
8
|
+
super(uiDevice);
|
9
|
+
}
|
10
|
+
|
11
|
+
@Override
|
12
|
+
protected boolean isTrue(int expectedCount, int actualCount) {
|
13
|
+
return actualCount > expectedCount;
|
14
|
+
}
|
15
|
+
|
16
|
+
}
|
@@ -1,7 +1,9 @@
|
|
1
1
|
package com.amplify.honeydew_server.httpd;
|
2
2
|
|
3
3
|
import android.util.Log;
|
4
|
-
import com.amplify.honeydew_server
|
4
|
+
import com.amplify.honeydew_server.ActionsExecutor;
|
5
|
+
import com.amplify.honeydew_server.Command;
|
6
|
+
import com.amplify.honeydew_server.Result;
|
5
7
|
import com.google.gson.Gson;
|
6
8
|
import com.google.gson.reflect.TypeToken;
|
7
9
|
import fi.iki.elonen.NanoHTTPD;
|
@@ -10,15 +12,20 @@ import java.io.IOException;
|
|
10
12
|
import java.lang.reflect.Type;
|
11
13
|
import java.util.Map;
|
12
14
|
|
15
|
+
import static java.lang.String.format;
|
16
|
+
|
13
17
|
public class RemoteCommandReceiver extends NanoHTTPD {
|
14
18
|
|
15
19
|
private static final String PLAIN_TEXT = "plain/text";
|
20
|
+
public static final Type ARGUMENTS_COLLECTION_TYPE = new TypeToken<Map<String, Object>>() {
|
21
|
+
}.getType();
|
16
22
|
private final ActionsExecutor actionsExecutor;
|
17
23
|
|
18
|
-
private final Response
|
19
|
-
private final Response
|
20
|
-
private final Response
|
24
|
+
private static final Response OK = new Response("honeydew-server is awaiting commands");
|
25
|
+
private static final Response TERMINATED = new Response("honeydew-server is stopping");
|
26
|
+
private final Response BAD_REQUEST = new Response(Response.Status.BAD_REQUEST, PLAIN_TEXT, "Unsupported command");
|
21
27
|
|
28
|
+
private static final String TAG = RemoteCommandReceiver.class.getSimpleName();
|
22
29
|
private final Gson jsonParser = new Gson();
|
23
30
|
|
24
31
|
public RemoteCommandReceiver(ActionsExecutor actionsExecutor) throws IOException, InterruptedException {
|
@@ -29,35 +36,43 @@ public class RemoteCommandReceiver extends NanoHTTPD {
|
|
29
36
|
@Override
|
30
37
|
public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> params, Map<String, String> files) {
|
31
38
|
if (method == Method.POST && uri.equalsIgnoreCase("/terminate")) {
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
} else if (method == Method.POST && uri.equalsIgnoreCase("/command")) {
|
39
|
+
return terminate();
|
40
|
+
}
|
41
|
+
if (method == Method.POST && uri.equalsIgnoreCase("/command")) {
|
36
42
|
return performCommand(params);
|
37
|
-
} else if (method == Method.GET && uri.equalsIgnoreCase("/status")) {
|
38
|
-
Log.d(getClass().getName(), "Got status request, responding OK");
|
39
|
-
return statusOkResponse;
|
40
43
|
}
|
44
|
+
if (method == Method.GET && uri.equalsIgnoreCase("/status")) {
|
45
|
+
return status();
|
46
|
+
}
|
47
|
+
|
48
|
+
return BAD_REQUEST;
|
49
|
+
}
|
50
|
+
|
51
|
+
private Response status() {
|
52
|
+
Log.d(TAG, "Got status request, responding OK");
|
53
|
+
return OK;
|
54
|
+
}
|
41
55
|
|
42
|
-
|
56
|
+
private Response terminate() {
|
57
|
+
Log.d(TAG, "Got terminate request, finishing up...");
|
58
|
+
stop();
|
59
|
+
return TERMINATED;
|
43
60
|
}
|
44
61
|
|
45
62
|
private Response performCommand(Map<String, String> params) {
|
46
63
|
String action = params.get("action");
|
47
64
|
String argumentJson = params.get("arguments");
|
48
|
-
|
49
|
-
Map<String, Object> arguments = jsonParser.fromJson(argumentJson, argumentCollectionType);
|
65
|
+
Map<String, Object> arguments = jsonParser.fromJson(argumentJson, ARGUMENTS_COLLECTION_TYPE);
|
50
66
|
|
51
|
-
Log.i(
|
67
|
+
Log.i(TAG, format("Performing action %s: %s for %s", action, argumentJson, params.toString()));
|
52
68
|
try {
|
53
69
|
Result result = actionsExecutor.execute(new Command(action, arguments));
|
54
70
|
if (result.success) {
|
55
71
|
return new Response(result.description);
|
56
|
-
} else {
|
57
|
-
return new Response(Response.Status.NO_CONTENT, null, (String) null);
|
58
72
|
}
|
59
|
-
|
60
|
-
|
73
|
+
return new Response(Response.Status.NO_CONTENT, null, (String) null);
|
74
|
+
} catch (Exception exception) {
|
75
|
+
Log.e(TAG, format("Server error while processing command %s: %s", action, exception.toString()));
|
61
76
|
return new Response(Response.Status.INTERNAL_ERROR, PLAIN_TEXT, exception.getMessage());
|
62
77
|
}
|
63
78
|
}
|
Binary file
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: honeydew
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.21.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Selvakumar Natesan
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2013-08-
|
15
|
+
date: 2013-08-22 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: activesupport
|
@@ -182,6 +182,7 @@ files:
|
|
182
182
|
- server/src/main/java/com/amplify/honeydew_server/Command.java
|
183
183
|
- server/src/main/java/com/amplify/honeydew_server/Result.java
|
184
184
|
- server/src/main/java/com/amplify/honeydew_server/TestRunner.java
|
185
|
+
- server/src/main/java/com/amplify/honeydew_server/actions/AbstractChildCountAction.java
|
185
186
|
- server/src/main/java/com/amplify/honeydew_server/actions/Click.java
|
186
187
|
- server/src/main/java/com/amplify/honeydew_server/actions/ClickAndWaitForNewWindow.java
|
187
188
|
- server/src/main/java/com/amplify/honeydew_server/actions/DumpWindowHierarchy.java
|
@@ -189,6 +190,7 @@ files:
|
|
189
190
|
- server/src/main/java/com/amplify/honeydew_server/actions/InspectOptionInSettingsMenu.java
|
190
191
|
- server/src/main/java/com/amplify/honeydew_server/actions/IsButtonPresent.java
|
191
192
|
- server/src/main/java/com/amplify/honeydew_server/actions/IsChildCountEqualTo.java
|
193
|
+
- server/src/main/java/com/amplify/honeydew_server/actions/IsChildCountGreaterThan.java
|
192
194
|
- server/src/main/java/com/amplify/honeydew_server/actions/IsElementWithNestedTextPresent.java
|
193
195
|
- server/src/main/java/com/amplify/honeydew_server/actions/IsOptionInSettingsMenuDisabled.java
|
194
196
|
- server/src/main/java/com/amplify/honeydew_server/actions/IsOptionInSettingsMenuEnabled.java
|
@@ -210,7 +212,7 @@ files:
|
|
210
212
|
- spec/honeydew/device_matchers_spec.rb
|
211
213
|
- spec/honeydew/device_spec.rb
|
212
214
|
- spec/spec_helper.rb
|
213
|
-
- server/target/honeydew-server-0.
|
215
|
+
- server/target/honeydew-server-0.21.0.jar
|
214
216
|
homepage:
|
215
217
|
licenses: []
|
216
218
|
post_install_message:
|
Binary file
|