Skip to content

Commit

Permalink
refactor: Deprecate custom event listener implementation and default …
Browse files Browse the repository at this point in the history
…to the one provided by Selenium4 (#1541)
  • Loading branch information
mykola-mokhnach authored Oct 24, 2021
1 parent ac625c3 commit 1a8ff99
Show file tree
Hide file tree
Showing 56 changed files with 716 additions and 2,224 deletions.
213 changes: 69 additions & 144 deletions docs/The-event_firing.md
Original file line number Diff line number Diff line change
@@ -1,151 +1,76 @@
since 4.1.0
since v8.0.0

# The purpose

This feature allows end user to organize the event logging on the client side. Also this feature may be useful in a binding with standard or custom reporting
frameworks.


# The API

The API was designed the way which allows end user to select events (searching, navigation, exception throwing etc.) which should be listened to. It contains
the following list of interfaces (new items may be added further):

- `io.appium.java_client.events.api.Listener` is the basic interface
- `io.appium.java_client.events.api.general.AlertEventListener` is for the listening to alerts
- `io.appium.java_client.events.api.general.ElementEventListener` is for the listening to actions related to elements
- `io.appium.java_client.events.api.general.JavaScriptEventListener` is for the listening to java script executing
- `io.appium.java_client.events.api.general.ListensToException` is for the listening to exceptions which are thrown
- `io.appium.java_client.events.api.general.NavigationEventListener` is for the listening to events related to navigation
- `io.appium.java_client.events.api.general.SearchingEventListener` is for the listening to events related to the searching.
- `io.appium.java_client.events.api.general.WindowEventListener` is for the listening to actions on a window
- `io.appium.java_client.events.api.mobile.ContextEventListener` is for the listening to the switching to mobile context
- `io.appium.java_client.events.api.mobile.RotationEventListener` is for the listening to screen rotation
- `io.appium.java_client.events.api.general.AppiumWebDriverEventListener` was added to provide the compatibility with
user's implementation of `org.openqa.selenium.support.events.WebDriverEventListener`. Also it extends some interfaces above.

# Briefly about the engine.

This is pretty similar solution as the `org.openqa.selenium.support.events.EventFiringWebDriver` of the Selenium project. You
can read about this thing there [The blog post](https://seleniumworks.blogspot.com/2014/02/eventfiringwebdriver.html).

Here we were trying to improve existing drawbacks and restrictions using:

- API splitting, see above.

- the binding of some [Spring framework engines](https://spring.io/projects/spring-framework) with [AspectJ](https://en.wikipedia.org/wiki/AspectJ).

# How to use

It is easy.

```java
import io.appium.java_client.events.api.general.AlertEventListener;

public class AlertListener implements AlertEventListener {
...
}

...
import io.appium.java_client.events.api.general.ElementEventListener;

public class ElementListener implements ElementEventListener {
...
}

//and so on
...
import io.appium.java_client.events.EventFiringWebDriverFactory;
import io.appium.java_client.events.api.Listener;
...

AndroidDriver driver = new AndroidDriver(parameters);
driver = EventFiringWebDriverFactory.getEventFiringWebDriver(driver, new AlertListener(),
new ElementListener());

//or
AndroidDriver driver2 = new AndroidDriver(parameters);
List<Listener> listeners = new ArrayList<>();
listeners.add(new AlertListener());
listeners.add(new ElementListener());
driver = EventFiringWebDriverFactory.getEventFiringWebDriver(driver2, listeners);
```

## What if there are listeners which used everywhere by default.

In order to avoid the repeating actions an end user is free to do these things:

- create folders `/META-INF/services` and put the file `io.appium.java_client.events.api.Listener` there. Please read about
[SPI](https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html).

![image](https://cloud.githubusercontent.com/assets/4927589/16731325/24eab680-4780-11e6-8551-a3c72d4b9c38.png)

- define the list of default listeners at the `io.appium.java_client.events.api.Listener`

![image](https://cloud.githubusercontent.com/assets/4927589/16731509/2734a4e0-4781-11e6-81cb-ab64a5924c35.png)

And then it is enough
This feature allows end user to organize the event logging on the client side.
Also this feature may be useful in a binding with standard or custom reporting
frameworks. The feature has been introduced first since Selenium API v4.

```java

//and so on
...
import io.appium.java_client.events.EventFiringWebDriverFactory;
...

AndroidDriver driver = new AndroidDriver(parameters);
driver = EventFiringWebDriverFactory.getEventFiringWebDriver(driver);
```

If there are listeners defined externally when this collection is merged with default set of listeners.

# How to reuse customized WebDriverEventListener

If an end user has their own `org.openqa.selenium.support.events.WebDriverEventListener` implementation then in order to
make it compatible with this engine it is enough to do the following.


```java
import org.openqa.selenium.support.events.WebDriverEventListener;
import io.appium.java_client.events.api.general.AppiumWebDriverEventListener;

public class UsersWebDriverEventListener implements WebDriverEventListener, AppiumWebDriverEventListener {
...
}
```

or just

```java
import io.appium.java_client.events.api.general.AppiumWebDriverEventListener;

public class UsersWebDriverEventListener implements AppiumWebDriverEventListener {
...
}
```
# Also

As soon as Appium java client has *Java 8-style* API (methods with default implementation) there was provided the ability to get objects created by these interfaces (anonymous types) listenable. Also there is an option to make some objects (some single element that has been found, for example) listenable too.
# The API

There are two main entities used to implement events firing logic:
- [org.openqa.selenium.support.events.EventFiringDecorator](https://github.com/SeleniumHQ/selenium/blob/trunk/java/src/org/openqa/selenium/support/events/EventFiringDecorator.java) class
- [org.openqa.selenium.support.events.WebDriverListener](https://github.com/SeleniumHQ/selenium/blob/trunk/java/src/org/openqa/selenium/support/events/WebDriverListener.java) interface

## WebDriverListener

Classes that implement this interface are intended to be used with EventFiringDecorator.
This interface provides empty default implementation for all methods that do nothing.
You could easily extend that interface to add more methods that you'd like to listen to.
The strategy to add new/custom event listeners is the following. Let say there is a public `setOrientation`
method in the target WebDriver instance. Then you'd have to add `beforeSetOrientation` and/or
`afterSetOrientation` methods to your WebDriverListener descendant accepting single argument
of `WebDriver` type. If the target method accepts one or more arguments then these arguments
should also be added to the event listeners in the same order they are accepted by the original method,
but the very first argument should still be the firing WebDriver instance.

_Important_: Make sure that your implementation of WebDriverListener class is public
and that event listener methods are also public.

## EventFiringDecorator

This decorator creates a wrapper around an arbitrary WebDriver instance that notifies
registered listeners about events happening in this WebDriver and derived objects,
such as WebElements and Alert.
Listeners should implement WebDriverListener. It supports three types of events:
- "before"-event: a method is about to be called;
- "after"-event: a method was called successfully and returned some result;
- "error"-event: a method was called and thrown an exception.

To use this decorator you have to prepare a listener, create a decorator using this listener,
decorate the original WebDriver instance with this decorator and use the new WebDriver instance
created by the decorator instead of the original one:
```java
import static io.appium.java_client.events.EventFiringObjectFactory.getEventFiringObject;
...

AppiumDriver<AndroidElement> appiumDriver = new AppiumDriver<AndroidElement>(parameters);
FindsByAndroidUIAutomator<AndroidElement> findsByAndroidUIAutomator =
new FindsByAndroidUIAutomator<AndroidElement>() {

@Override
public AndroidElement findElement(String by, String using) {
return appiumDriver.findElement(String by, String using);
}

@Override
public List<AndroidElement> findElements(String by, String using) {
return appiumDriver.findElements(by, using);
}
};

findsByAndroidUIAutomator =
getEventFiringObject(findsByAndroidUIAutomator, appiumDriver, listeners);
WebDriver original = new AndroidDriver();
// it is expected that MyListener class implements WebDriverListener
// interface or its descendant
WebDriverListener listener = new MyListener();
WebDriver decorated = new EventFiringDecorator(listener).decorate(original);
// the next call is going to fire:
// - beforeAnyCall
// - beforeAnyWebDriverCall
// - beforeGet
// - afterGet
// - afterAnyWebDriverCall
// - afterAnyCall
// events in the listener instence (in this order)
decorated.get("http://example.com/");
// the next call is going to fire:
// - beforeAnyCall
// - beforeAnyWebDriverCall
// - beforeFindElement
// - afterFindElement
// - afterAnyWebDriverCall
// - afterAnyCall
// events in the listener instence (in this order)
WebElement header = decorated.findElement(By.tagName("h1"));
// if an error happens during any of these calls the the onError event is fired
```
The instance of WebDriver created by the decorator implements all the same interfaces
as the original driver. A listener can subscribe to "specific" or "generic" events (or both).
A "specific" event correspond to a single specific method, a "generic" event correspond to any
method called in a class or in any class. To subscribe to a "specific" event a listener should
implement a method with a name derived from the target method to be watched. The listener methods
for "before"-events receive the parameters passed to the decorated method. The listener
methods for "after"-events receive the parameters passed to the decorated method as well as the
result returned by this method.
4 changes: 4 additions & 0 deletions src/main/java/io/appium/java_client/events/DefaultAspect.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
import java.util.Collection;
import java.util.List;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
@SuppressWarnings("unused")
@Aspect
class DefaultAspect {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
import java.util.ArrayList;
import java.util.List;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
class DefaultBeanConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
import java.util.Collection;
import java.util.List;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
class DefaultListener
implements Listener, AppiumWebDriverEventListener, ListensToException, SearchingEventListener,
NavigationEventListener, JavaScriptEventListener, ElementEventListener, AlertEventListener,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
import java.util.List;
import java.util.ServiceLoader;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public class EventFiringObjectFactory {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@

import java.util.Collection;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public class EventFiringWebDriverFactory {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
import java.lang.reflect.Method;
import java.util.List;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
class ListenerInvocationHandler implements InvocationHandler {

private final List<Listener> listeners;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/io/appium/java_client/events/api/Listener.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

/**
* This interface just marks event listeners.
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public interface Listener {
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
import org.openqa.selenium.Alert;
import org.openqa.selenium.WebDriver;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public interface AlertEventListener extends Listener {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import io.appium.java_client.events.api.Listener;
import org.openqa.selenium.support.events.WebDriverEventListener;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public interface AppiumWebDriverEventListener extends Listener, WebDriverEventListener, ListensToException,
SearchingEventListener, NavigationEventListener,
JavaScriptEventListener, ElementEventListener {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public interface ElementEventListener extends Listener {
/**
* Called before {@link WebElement#click WebElement.click()}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import io.appium.java_client.events.api.Listener;
import org.openqa.selenium.WebDriver;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public interface JavaScriptEventListener extends Listener {
/**
* Called before
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import io.appium.java_client.events.api.Listener;
import org.openqa.selenium.WebDriver;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public interface ListensToException extends Listener {
/**
* Called whenever an exception would be thrown.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import io.appium.java_client.events.api.Listener;
import org.openqa.selenium.WebDriver;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public interface NavigationEventListener extends Listener {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public interface SearchingEventListener extends Listener {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import org.openqa.selenium.Point;
import org.openqa.selenium.WebDriver;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public interface WindowEventListener extends Listener {
/**
* This action will be performed each time before {@link WebDriver.Window#setSize(Dimension)}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import io.appium.java_client.events.api.Listener;
import org.openqa.selenium.WebDriver;

/**
* Deprecated. Use EventFiringDecorator and WebDriverListener instead.
*/
@Deprecated
public interface ContextEventListener extends Listener {

/**
Expand Down
Loading

0 comments on commit 1a8ff99

Please sign in to comment.