Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement swipe and click actions #372

Merged
merged 14 commits into from
Jan 21, 2019
23 changes: 11 additions & 12 deletions espresso-server/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ android {

buildTypes {
release {
minifyEnabled true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

}
}

Expand All @@ -33,11 +35,11 @@ dependencies {
testImplementation "org.powermock:powermock-classloading-xstream:1.7.4"
testImplementation "org.powermock:powermock-module-junit4-rule:1.7.4"
testImplementation "org.powermock:powermock-module-junit4:1.7.4"
testImplementation 'androidx.test.espresso:espresso-contrib:3.1.0'
testImplementation 'androidx.test.espresso:espresso-core:3.1.0'
testImplementation 'androidx.test.espresso:espresso-contrib:3.1.1'
testImplementation 'androidx.test.espresso:espresso-core:3.1.1'
testImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
testImplementation 'androidx.test:core:1.0.0'
testImplementation 'androidx.test:runner:1.1.0'
testImplementation 'androidx.test:core:1.1.0'
testImplementation 'androidx.test:runner:1.1.1'
testImplementation 'com.google.code.gson:gson:2.8.5'
testImplementation 'javax.ws.rs:jsr311-api:1.1.1'
testImplementation 'junit:junit:4.12'
Expand All @@ -47,11 +49,11 @@ dependencies {
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"

androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
androidTestImplementation 'androidx.test:core:1.0.0'
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:core:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'com.google.code.gson:gson:2.8.5'
androidTestImplementation 'javax.ws.rs:jsr311-api:1.1.1'
androidTestImplementation 'org.nanohttpd:nanohttpd-webserver:2.3.1'
Expand All @@ -62,6 +64,3 @@ dependencies {
tasks.withType(Test) {
systemProperty "skipespressoserver", "true"
}
repositories {
mavenCentral()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.appium.espressoserver.lib.handlers

import androidx.test.espresso.action.GeneralClickAction
import io.appium.espressoserver.lib.handlers.exceptions.AppiumException
import io.appium.espressoserver.lib.model.MobileClickActionParams
import io.appium.espressoserver.lib.model.Element
import io.appium.espressoserver.lib.viewaction.UiControllerPerformer
import io.appium.espressoserver.lib.viewaction.UiControllerRunnable

class MobileClickAction : RequestHandler<MobileClickActionParams, Void> {

@Throws(AppiumException::class)
override fun handle(params: MobileClickActionParams): Void? {
val runnable = UiControllerRunnable { uiController ->
val clickAction = GeneralClickAction(
params.tapper,
params.coordinatesProvider,
params.precisionDescriber,
params.inputDevice,
params.buttonState
);
clickAction.perform(uiController, Element.getViewById(params.elementId))
}
UiControllerPerformer(runnable).run()
return null
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.appium.espressoserver.lib.handlers

import androidx.test.espresso.action.GeneralSwipeAction

import io.appium.espressoserver.lib.handlers.exceptions.AppiumException
import io.appium.espressoserver.lib.handlers.exceptions.InvalidArgumentException
import io.appium.espressoserver.lib.model.Element
import io.appium.espressoserver.lib.model.MobileSwipeParams

import androidx.test.espresso.action.ViewActions.swipeDown
import androidx.test.espresso.action.ViewActions.swipeLeft
import androidx.test.espresso.action.ViewActions.swipeRight
import androidx.test.espresso.action.ViewActions.swipeUp
import io.appium.espressoserver.lib.helpers.AndroidLogger.logger
import io.appium.espressoserver.lib.model.MobileSwipeParams.Direction.*
import io.appium.espressoserver.lib.viewaction.UiControllerPerformer
import io.appium.espressoserver.lib.viewaction.UiControllerRunnable

class MobileSwipe : RequestHandler<MobileSwipeParams, Void> {

@Throws(AppiumException::class)
override fun handle(params: MobileSwipeParams): Void? {
// Get a reference to the view and call onData. This will automatically scroll to the view.
val viewInteraction = Element.getViewInteractionById(params.elementId)

if (params.direction != null) {
logger.info("Performing swipe action with direction '${params.direction}'")
when (params.direction) {
UP -> viewInteraction.perform(swipeUp())
DOWN -> viewInteraction.perform(swipeDown())
LEFT -> viewInteraction.perform(swipeLeft())
RIGHT -> viewInteraction.perform(swipeRight())
else -> throw InvalidArgumentException("Direction cannot be ${params.direction}");
}
} else if (params.swiper != null) {

val runnable = UiControllerRunnable { uiController ->
val swipeAction = GeneralSwipeAction(
params.swiper,
params.startCoordinates,
params.endCoordinates,
params.precisionDescriber
)
logger.info("""
Performing general swipe action with parameters
swiper=[${params.swiper}] startCoordinates=[${params.startCoordinates}]
endCoordinates=[${params.endCoordinates}] precisionDescriber=[${params.precisionDescriber}]
""".trimIndent())
swipeAction.perform(uiController, Element.getViewById(params.elementId))
}
UiControllerPerformer(runnable).run()
}

return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.appium.espressoserver.lib.helpers

import com.google.gson.JsonObject
import com.google.gson.JsonParseException
import java.lang.IllegalArgumentException

class GsonParserHelpers {

inline fun <reified T : Enum<T>> parseEnum(jsonObj: JsonObject, propName: String,
helperMessage: String = "", defaultValue: T? = null): T? {
val property = jsonObj.get(propName)
if (property != null) {
val propValueAsString = property.asString.toUpperCase()
try {
return enumValueOf<T>(propValueAsString)
} catch (e: IllegalArgumentException) {
throw JsonParseException(""""
'${propValueAsString}' is not a valid '${propName}' type. ${helperMessage}
""".trimIndent());
}
}
return defaultValue
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;

import java.util.Map;

Expand Down Expand Up @@ -58,6 +59,7 @@
import io.appium.espressoserver.lib.handlers.HideKeyboard;
import io.appium.espressoserver.lib.handlers.Keys;
import io.appium.espressoserver.lib.handlers.MobileBackdoor;
import io.appium.espressoserver.lib.handlers.MobileClickAction;
import io.appium.espressoserver.lib.handlers.MobileViewFlash;
import io.appium.espressoserver.lib.handlers.MobileSwipe;
import io.appium.espressoserver.lib.handlers.MultiTouchAction;
Expand Down Expand Up @@ -101,6 +103,7 @@
import io.appium.espressoserver.lib.http.response.BaseResponse;
import io.appium.espressoserver.lib.model.AppiumParams;
import io.appium.espressoserver.lib.model.AppiumStatus;
import io.appium.espressoserver.lib.model.MobileClickActionParams;
import io.appium.espressoserver.lib.model.DrawerActionParams;
import io.appium.espressoserver.lib.model.EditorActionParams;
import io.appium.espressoserver.lib.model.ElementValueParams;
Expand Down Expand Up @@ -229,6 +232,7 @@ class Router {
routeMap.addRoute(new RouteDefinition(Method.POST, "/session/:sessionId/appium/execute_mobile/backdoor", new MobileBackdoor(), MobileBackdoorParams.class));
routeMap.addRoute(new RouteDefinition(Method.POST, "/session/:sessionId/appium/execute_mobile/:elementId/flash", new MobileViewFlash(), ViewFlashParams.class));
routeMap.addRoute(new RouteDefinition(Method.POST, "/session/:sessionId/appium/execute_mobile/uiautomator", new Uiautomator(), UiautomatorParams.class));
routeMap.addRoute(new RouteDefinition(Method.POST, "/session/:sessionId/appium/execute_mobile/:elementId/click_action", new MobileClickAction(), MobileClickActionParams.class));

// Not implemented
routeMap.addRoute(new RouteDefinition(Method.POST, "/session/:sessionId/touch/flick", new NotYetImplemented(), AppiumParams.class));
Expand Down Expand Up @@ -292,22 +296,29 @@ public BaseResponse route(String uri, Method method, Map<String, String> files)
// Get the appium params
String postJson = files.get("postData");

AppiumParams appiumParams;
if (postJson == null) {
appiumParams = new AppiumParams();
} else {
logger.debug(String.format("Got raw post data: %s", abbreviate(postJson, 300)));
appiumParams = paramClass.cast((new Gson()).fromJson(postJson, paramClass));
}
appiumParams.initUriMapping(uriParams);
try {

// Validate the sessionId
if (appiumParams.getSessionId() != null && !appiumParams.getSessionId().equals(Session.getGlobalSession().getId())) {
return new AppiumResponse<>(AppiumStatus.UNKNOWN_ERROR, "Invalid session ID " + appiumParams.getSessionId());
}
// Parse the parameters
AppiumParams appiumParams;
if (postJson == null) {
appiumParams = new AppiumParams();
} else {
logger.debug(String.format("Got raw post data: %s", abbreviate(postJson, 300)));
try {
appiumParams = paramClass.cast((new Gson()).fromJson(postJson, paramClass));
} catch (JsonParseException e) {
// If failed to parse params, throw an invalid argument exception
return new AppiumResponse<>(AppiumStatus.INVALID_ARGUMENT, Log.getStackTraceString(e));
}
}
appiumParams.initUriMapping(uriParams);

// Create the result
try {
// Validate the sessionId
if (appiumParams.getSessionId() != null && !appiumParams.getSessionId().equals(Session.getGlobalSession().getId())) {
return new AppiumResponse<>(AppiumStatus.UNKNOWN_ERROR, "Invalid session ID " + appiumParams.getSessionId());
}

// Execute the matching handler
Object handlerResult = handler.handle(appiumParams);
String sessionId = appiumParams.getSessionId();

Expand All @@ -316,6 +327,7 @@ public BaseResponse route(String uri, Method method, Map<String, String> files)
sessionId = ((Session) handlerResult).getId();
}

// Construct the response and serve it
AppiumResponse appiumResponse = new AppiumResponse<>(AppiumStatus.SUCCESS, handlerResult, sessionId);
logger.debug(String.format("Finished processing %s request for '%s'", method, uri));
return appiumResponse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ public static Server getInstance() {
}

private Response buildFixedLengthResponse(BaseResponse response) {
GsonBuilder gsonBuilder = new GsonBuilder().serializeNulls();
gsonBuilder.registerTypeAdapter(AppiumStatus.class, new AppiumStatusAdapter());
GsonBuilder gsonBuilder = new GsonBuilder()
.serializeNulls()
.registerTypeAdapter(AppiumStatus.class, new AppiumStatusAdapter());
return newFixedLengthResponse(response.getHttpStatus(),
MediaType.APPLICATION_JSON, gsonBuilder.create().toJson(response));
}
Expand Down
Loading