Skip to content

Commit 26d7cfd

Browse files
authored
Mini blog on java support events class removal (#1533)
* add mini blog * Update website_and_docs/content/blog/2023/java-removal-of-deprecated-events-classes.md --------- Co-authored-by: Diego Molina <[email protected]> Merging as SeleniumHQ/selenium#13200 got merged. [deploy site]
1 parent 27e3638 commit 26d7cfd

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---
2+
title: Removal of AbstractEventListener + EventFiringWebDriver + WebDriverEventListener
3+
date: 2023-12-08
4+
tags: [java, events, webdriver]
5+
categories: [general]
6+
author: Oscar Devora ([@oscardevora](https://twitter.com/Yo_Oscarr))
7+
description: >
8+
This blog will go over some examples on how to transition code that uses the aforementioned classes.
9+
---
10+
11+
### Upgrading to WebDriverListener and EventFiringDecorator
12+
Decorating the webdriver
13+
14+
```java
15+
new EventFiringWebDriver(driver); // Old approach
16+
new EventFiringDecorator().decorate(driver); // New approach
17+
```
18+
### Implementing method wrappers
19+
One may find the need to have their own custom implementations be used for underlying decorated method calls. An example may be wanting to use your own findElement implementation to store metadata from web elements. One can go down a deep rabbit hole of decorators ( extending WebDriverDecorator and such ), so to keep things simple we will extend EventFiringDecorator since we want a single decorator to handle all our listener events.
20+
21+
```java
22+
public class WebDriverWrapper implements WebDriver {
23+
private final WebDriver driver;
24+
WebDriverWrapper(WebDriver driver) {
25+
this.driver = driver;
26+
}
27+
// custom implementation goes here
28+
@Override
29+
public WebElement findElement(final By by) {
30+
// custom implementation goes here
31+
return driver.findElement(by);
32+
}
33+
}
34+
35+
public class testDecorator extends EventFiringDecorator<WebDriver> {
36+
37+
@Override
38+
public Object call(Decorated<?> target, Method method, Object[] args) throws Throwable {
39+
String methodName = method.getName();
40+
if ("findElement".equals(methodName)) {
41+
WebDriverWrapper newDriver = new WebDriverWrapper((WebDriver) target.getOriginal());
42+
return newDriver.findElement((By) args[0]);
43+
}
44+
return super.call(target, method, args);
45+
}
46+
}
47+
```
48+
Some notes about the above example, we are only overriding the ‘general’ call method and checking the method name against every call made.
49+
Without going too deep decorators one can also override calls made by class instances to offer a more targeted approach.
50+
Just to expose some more functionality, let's modify our example.
51+
We can modify WebElement context since we might care about child elements and elements found by WebDriver ( WebDriver and WebElement both extend the SearchContext ).
52+
53+
```java
54+
public class WebElementWrapper implements WebElement {
55+
private final WebElement element;
56+
WebElementWrapper(WebElement element) {
57+
this.element = element;
58+
}
59+
@Override
60+
public WebElement findElement(final By by) {
61+
// custom implementation goes here
62+
return element.findElement(by);
63+
}
64+
}
65+
66+
public class WebElementDecorator extends EventFiringDecorator<WebDriver> {
67+
@Override
68+
public Decorated<WebElement> createDecorated(WebElement original) {
69+
return new DefaultDecorated<>(original, this) {
70+
@Override
71+
public Object call(Method method, Object[] args) throws Throwable {
72+
String methodName = method.getName();
73+
if ("findElement".equals(methodName)) {
74+
// custom implementation goes here
75+
WebElementWrapper element = new WebElementWrapper(getOriginal());
76+
return element.findElement((By) args[0]);
77+
}
78+
return super.call(method, args);
79+
}
80+
};
81+
}
82+
}
83+
```
84+
In the sample above, we are still doing a very similar approach of overriding the call method but now we are also targeting WebElement instances.
85+
### Registering Listeners
86+
87+
```java
88+
new EventFiringWebDriver(driver).register(listener1).register(listener2); // Old approach
89+
new EventFiringDecorator(listener1, listener2); // New approach
90+
```
91+
### Listening to Events
92+
A quality of life change that is featured in WebDriverListener class is the use of ‘default’.
93+
In Java, the `default` keyword, when used in the context of an interface method, indicates that the method has a default implementation.
94+
If a class implementing the interface chooses not to override the method, it will inherit the default implementation.
95+
This change allows for splitting up listeners without needing to implement the unnecessary methods you don't need or care about.
96+
97+
#### Listening to specific events using before/after methods call
98+
```java
99+
// Old approach
100+
public class AlertListener implements WebDriverEventListener {
101+
@Override
102+
public void beforeAlertAccept(final WebDriver driver) {
103+
// custom implementation goes here
104+
}
105+
// implement every method in interface
106+
}
107+
108+
// New approach
109+
public class AlertListener implements WebDriverListener {
110+
@Override
111+
public void beforeAccept(Alert alert) {
112+
// custom implementation goes here
113+
}
114+
// does not need to implement every method in interface
115+
}
116+
```
117+
#### Listening to Generic Events
118+
A change that was brought on is the ability to listen to generic events.
119+
One use case is logging information in a parallelized test suite.
120+
Rather than create a listener and override every method to add a simple log statement, there is now a simpler alternative of overriding one method call.
121+
Below I override beforeAnyCall, but afterAnyCall exists as well which also has the results of the call to the decorated method.
122+
123+
```java
124+
public class Listener implements WebDriverEventListener {
125+
private static final Logger LOGGER = Logger.getLogger(Listener.class.getName());
126+
127+
@Override
128+
public void beforeAnyCall(Object target, Method method, Object[] args) {
129+
logger.debug("Thread: " + Thread.currentThread().getName() +
130+
" | Method Name: " + method.getName() +
131+
" | Method Args: " + Arrays.toString(args));
132+
}
133+
}
134+
```
135+
136+
There also was an addition listening to more specific generic events.
137+
Going back to the logging example, beforeAnyCall is a good method for debugging information or tracking the actions of a thread but might generate too much noise.
138+
In the same use case we might only care about WebDriver or WebElement calls.
139+
One can override instances of WebDriver and derived objects( WebElement, Alert, etc.) for before/after events.
140+
141+
```java
142+
public class Listener implements WebDriverEventListener {
143+
private static final Logger LOGGER = Logger.getLogger(Listener.class.getName());
144+
145+
@Override
146+
public void beforeAnyWebDriverCall(WebDriver driver, Method method, Object[] args) {
147+
logger.debug("Thread: " + Thread.currentThread().getName() +
148+
" | Method Name: " + method.getName() +
149+
" | Method Args: " + Arrays.toString(args));
150+
}
151+
152+
@Override
153+
public void beforeAnyWebElementCall(WebElement element, Method method, Object[] args) {
154+
logger.debug("Thread: " + Thread.currentThread().getName() +
155+
" | Method Name: " + method.getName() +
156+
" | Method Args: " + Arrays.toString(args));
157+
}
158+
}
159+
```
160+
161+
So that's some general examples on how to transition your code!
162+
Happy testing!
163+
164+

0 commit comments

Comments
 (0)