diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index c0c1aca0e..398779025 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -68,7 +68,7 @@ @SuppressWarnings("unchecked") public class AppiumDriver extends DefaultGenericMobileDriver implements ComparesImages, FindsByImage, FindsByCustom, - ExecutesDriverScript { + ExecutesDriverScript, LogsEvents { private static final ErrorHandler errorHandler = new ErrorHandler(new ErrorCodesMobile(), true); // frequently used command parameters diff --git a/src/main/java/io/appium/java_client/LogsEvents.java b/src/main/java/io/appium/java_client/LogsEvents.java new file mode 100644 index 000000000..7844b56a6 --- /dev/null +++ b/src/main/java/io/appium/java_client/LogsEvents.java @@ -0,0 +1,80 @@ +/* + * 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.java_client; + +import static io.appium.java_client.MobileCommand.GET_EVENTS; +import static io.appium.java_client.MobileCommand.LOG_EVENT; + +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.serverevents.CommandEvent; +import io.appium.java_client.serverevents.CustomEvent; +import io.appium.java_client.serverevents.TimedEvent; +import io.appium.java_client.serverevents.ServerEvents; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.remote.Response; + +public interface LogsEvents extends ExecutesMethod { + + /** + * Log a custom event on the Appium server. + * + * @since Appium 1.16 + * @param event the event to log + * @throws org.openqa.selenium.WebDriverException if there was a failure while executing the script + */ + default void logEvent(CustomEvent event) { + execute(LOG_EVENT, ImmutableMap.of("vendor", event.getVendor(), "event", event.getEventName())); + } + + /** + * Log a custom event on the Appium server. + * + * @since Appium 1.16 + * @return ServerEvents object wrapping up the various command and event timestamps + * @throws org.openqa.selenium.WebDriverException if there was a failure while executing the script + */ + default ServerEvents getEvents() { + Response response = execute(GET_EVENTS); + String jsonData = new Json().toJson(response.getValue()); + + //noinspection unchecked + Map value = (Map) response.getValue(); + + //noinspection unchecked + List commands = ((List>) value.get("commands")) + .stream() + .map((Map cmd) -> new CommandEvent( + (String) cmd.get("cmd"), + ((Long) cmd.get("startTime")), + ((Long) cmd.get("endTime")) + )) + .collect(Collectors.toList()); + + List events = value.keySet().stream() + .filter((String name) -> !name.equals("commands")) + .map((String name) -> { + //noinspection unchecked + return new TimedEvent(name, (List) value.get(name)); + }) + .collect(Collectors.toList()); + + return new ServerEvents(commands, events, jsonData); + } +} diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index ab0d3d756..58a3b6098 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -51,6 +51,8 @@ public class MobileCommand { protected static final String CLOSE_APP; protected static final String GET_DEVICE_TIME; protected static final String GET_SESSION; + protected static final String LOG_EVENT; + protected static final String GET_EVENTS; //region Applications Management protected static final String IS_APP_INSTALLED; @@ -128,6 +130,8 @@ public class MobileCommand { CLOSE_APP = "closeApp"; GET_DEVICE_TIME = "getDeviceTime"; GET_SESSION = "getSession"; + LOG_EVENT = "logCustomEvent"; + GET_EVENTS = "getLogEvents"; //region Applications Management IS_APP_INSTALLED = "isAppInstalled"; @@ -214,6 +218,10 @@ public class MobileCommand { postC("/session/:sessionId/appium/start_recording_screen")); commandRepository.put(STOP_RECORDING_SCREEN, postC("/session/:sessionId/appium/stop_recording_screen")); + commandRepository.put(GET_EVENTS, + postC("/session/:sessionId/appium/events")); + commandRepository.put(LOG_EVENT, + postC("/session/:sessionId/appium/log_event")); //region Applications Management commandRepository.put(IS_APP_INSTALLED, postC("/session/:sessionId/appium/device/app_installed")); diff --git a/src/main/java/io/appium/java_client/serverevents/CommandEvent.java b/src/main/java/io/appium/java_client/serverevents/CommandEvent.java new file mode 100644 index 000000000..960b4fddb --- /dev/null +++ b/src/main/java/io/appium/java_client/serverevents/CommandEvent.java @@ -0,0 +1,10 @@ +package io.appium.java_client.serverevents; + +import lombok.Data; + +@Data +public class CommandEvent { + public final String name; + public final long startTimestamp; + public final long endTimestamp; +} diff --git a/src/main/java/io/appium/java_client/serverevents/CustomEvent.java b/src/main/java/io/appium/java_client/serverevents/CustomEvent.java new file mode 100644 index 000000000..66bab4cb5 --- /dev/null +++ b/src/main/java/io/appium/java_client/serverevents/CustomEvent.java @@ -0,0 +1,9 @@ +package io.appium.java_client.serverevents; + +import lombok.Data; + +@Data +public class CustomEvent { + private String vendor; + private String eventName; +} \ No newline at end of file diff --git a/src/main/java/io/appium/java_client/serverevents/ServerEvents.java b/src/main/java/io/appium/java_client/serverevents/ServerEvents.java new file mode 100644 index 000000000..901241ce5 --- /dev/null +++ b/src/main/java/io/appium/java_client/serverevents/ServerEvents.java @@ -0,0 +1,19 @@ +package io.appium.java_client.serverevents; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import lombok.Data; + +@Data +public class ServerEvents { + + public final List commands; + public final List events; + public final String jsonData; + + public void save(Path output) throws IOException { + Files.write(output, this.jsonData.getBytes()); + } +} \ No newline at end of file diff --git a/src/main/java/io/appium/java_client/serverevents/TimedEvent.java b/src/main/java/io/appium/java_client/serverevents/TimedEvent.java new file mode 100644 index 000000000..dca5f1218 --- /dev/null +++ b/src/main/java/io/appium/java_client/serverevents/TimedEvent.java @@ -0,0 +1,10 @@ +package io.appium.java_client.serverevents; + +import java.util.List; +import lombok.Data; + +@Data +public class TimedEvent { + public final String name; + public final List occurrences; +} diff --git a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java index c82886807..022c8db50 100644 --- a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java @@ -51,6 +51,7 @@ public class BaseAndroidTest { capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability("eventTimings", true); driver = new AndroidDriver<>(service.getUrl(), capabilities); } diff --git a/src/test/java/io/appium/java_client/android/LogEventTest.java b/src/test/java/io/appium/java_client/android/LogEventTest.java new file mode 100644 index 000000000..ab4b0350a --- /dev/null +++ b/src/test/java/io/appium/java_client/android/LogEventTest.java @@ -0,0 +1,49 @@ +/* + * 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.java_client.android; + +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import io.appium.java_client.serverevents.CommandEvent; +import io.appium.java_client.serverevents.CustomEvent; +import io.appium.java_client.serverevents.TimedEvent; +import io.appium.java_client.serverevents.ServerEvents; +import org.hamcrest.Matchers; +import org.junit.Test; + +public class LogEventTest extends BaseAndroidTest { + + @Test + public void verifyLoggingCustomEvents() { + CustomEvent evt = new CustomEvent(); + evt.setEventName("funEvent"); + evt.setVendor("appium"); + driver.logEvent(evt); + ServerEvents events = driver.getEvents(); + boolean hasCustomEvent = events.events.stream().anyMatch((TimedEvent event) -> + event.name.equals("appium:funEvent") && + event.occurrences.get(0).intValue() > 0 + ); + boolean hasCommandName = events.commands.stream().anyMatch((CommandEvent event) -> + event.name.equals("logCustomEvent") + ); + assertTrue(hasCustomEvent); + assertTrue(hasCommandName); + assertThat(events.jsonData, Matchers.containsString("\"appium:funEvent\"")); + } +}