Skip to content

Commit 73b6263

Browse files
committed
[java] Allow EventFiringDecorator to throw exceptions #16470
WebDriverListener has now the throwsExceptions method to configure its behavior with regard to exception management. By default, it returns false, meaning exceptions are suppressed. If overridden to return true, exceptions occurred in the listener execution will be rethrown, so to allow users to manage them on their side. Fixes #16470
1 parent 912f35e commit 73b6263

File tree

4 files changed

+222
-6
lines changed

4 files changed

+222
-6
lines changed

java/src/org/openqa/selenium/support/events/EventFiringDecorator.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,10 @@
154154
*
155155
* <p>Just be careful to not block the current thread in a listener method!
156156
*
157-
* <p>Listeners can't affect driver behavior too much. They can't throw any exceptions (they can,
158-
* but the decorator suppresses these exceptions), can't prevent execution of the decorated methods,
159-
* can't modify parameters and results of the methods.
157+
* <p>Listeners can't affect driver behavior too much. They can't prevent execution of the decorated
158+
* methods, can't modify parameters and results of the methods. They can throw exceptions only if
159+
* configured to do so by overriding {@link WebDriverListener#throwsExceptions}. By default,
160+
* exceptions occurred in listeners execution are suppressed.
160161
*
161162
* <p>Decorators that modify the behaviour of the underlying drivers should be implemented by
162163
* extending {@link WebDriverDecorator}, not by creating sophisticated listeners.
@@ -217,6 +218,10 @@ private void fireBeforeEvents(
217218
listener.beforeAnyCall(target.getOriginal(), method, args);
218219
} catch (Throwable t) {
219220
LOG.log(Level.WARNING, t.getMessage(), t);
221+
222+
if (listener.throwsExceptions()) {
223+
throw new WebDriverListenerException("beforeAnyCall", t);
224+
}
220225
}
221226

222227
try {
@@ -240,6 +245,10 @@ private void fireBeforeEvents(
240245
}
241246
} catch (Throwable t) {
242247
LOG.log(Level.WARNING, t.getMessage(), t);
248+
249+
if (listener.throwsExceptions()) {
250+
throw new WebDriverListenerException(method, t);
251+
}
243252
}
244253

245254
String methodName = createEventMethodName("before", method.getName());
@@ -291,12 +300,20 @@ private void fireAfterEvents(
291300
}
292301
} catch (Throwable t) {
293302
LOG.log(Level.WARNING, t.getMessage(), t);
303+
304+
if (listener.throwsExceptions()) {
305+
throw new WebDriverListenerException(method, t);
306+
}
294307
}
295308

296309
try {
297310
listener.afterAnyCall(target.getOriginal(), method, args, res);
298311
} catch (Throwable t) {
299312
LOG.log(Level.WARNING, t.getMessage(), t);
313+
314+
if (listener.throwsExceptions()) {
315+
throw new WebDriverListenerException("afterAnyCall", t);
316+
}
300317
}
301318
}
302319

@@ -355,6 +372,10 @@ private void callListenerMethod(Method m, WebDriverListener listener, Object[] a
355372
m.invoke(listener, args);
356373
} catch (Throwable t) {
357374
LOG.log(Level.WARNING, t.getMessage(), t);
375+
376+
if (listener.throwsExceptions()) {
377+
throw new WebDriverListenerException(m, t);
378+
}
358379
}
359380
}
360381
}

java/src/org/openqa/selenium/support/events/WebDriverListener.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@
5050
@Beta
5151
public interface WebDriverListener {
5252

53+
// Listener configuration
54+
55+
/**
56+
* This method configures the behavior of the listener with regard to exceptions occurred during
57+
* its execution. By default, exceptions are suppressed.
58+
*
59+
* @return false by default. Override it and return true to throw exceptions instead.
60+
*/
61+
default boolean throwsExceptions() {
62+
return false;
63+
}
64+
5365
// Global
5466

5567
/**
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.support.events;
19+
20+
import java.lang.reflect.Method;
21+
import java.util.Arrays;
22+
23+
public class WebDriverListenerException extends RuntimeException {
24+
25+
public WebDriverListenerException(String message, Throwable cause) {
26+
super(message, cause);
27+
}
28+
29+
public WebDriverListenerException(Method method, Throwable cause) {
30+
super(
31+
"Exception executing listener method "
32+
+ method.getDeclaringClass().getSimpleName()
33+
+ "#"
34+
+ method.getName()
35+
+ " with parameter types "
36+
+ Arrays.toString(
37+
Arrays.stream(method.getParameterTypes()).map(Class::getSimpleName).toArray()),
38+
cause);
39+
}
40+
}

java/test/org/openqa/selenium/support/events/EventFiringDecoratorTest.java

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717

1818
package org.openqa.selenium.support.events;
1919

20-
import static org.assertj.core.api.Assertions.assertThat;
21-
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
22-
import static org.assertj.core.api.Assertions.assertThatNoException;
20+
import static org.assertj.core.api.Assertions.*;
2321
import static org.mockito.ArgumentMatchers.any;
2422
import static org.mockito.Mockito.doNothing;
2523
import static org.mockito.Mockito.mock;
@@ -1050,6 +1048,30 @@ public void beforeAnyCall(Object target, Method method, Object[] args) {
10501048
assertThatNoException().isThrownBy(decorated::getWindowHandle);
10511049
}
10521050

1051+
@Test
1052+
void shouldReThrowExceptionInBeforeAnyCall() {
1053+
WebDriver driver = mock(WebDriver.class);
1054+
WebDriverListener listener =
1055+
new WebDriverListener() {
1056+
1057+
@Override
1058+
public boolean throwsExceptions() {
1059+
return true;
1060+
}
1061+
1062+
@Override
1063+
public void beforeAnyCall(Object target, Method method, Object[] args) {
1064+
throw new RuntimeException("listener");
1065+
}
1066+
};
1067+
1068+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1069+
1070+
assertThatExceptionOfType(WebDriverListenerException.class)
1071+
.isThrownBy(decorated::getWindowHandle)
1072+
.withMessage("beforeAnyCall");
1073+
}
1074+
10531075
@Test
10541076
void shouldSuppressExceptionInBeforeClassMethodCall() {
10551077
WebDriver driver = mock(WebDriver.class);
@@ -1066,6 +1088,30 @@ public void beforeAnyWebDriverCall(WebDriver driver, Method method, Object[] arg
10661088
assertThatNoException().isThrownBy(decorated::getWindowHandle);
10671089
}
10681090

1091+
@Test
1092+
void shouldReThrowExceptionInBeforeClassMethodCall() {
1093+
WebDriver driver = mock(WebDriver.class);
1094+
WebDriverListener listener =
1095+
new WebDriverListener() {
1096+
1097+
@Override
1098+
public boolean throwsExceptions() {
1099+
return true;
1100+
}
1101+
1102+
@Override
1103+
public void beforeAnyWebDriverCall(WebDriver driver, Method method, Object[] args) {
1104+
throw new RuntimeException("listener");
1105+
}
1106+
};
1107+
1108+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1109+
1110+
assertThatExceptionOfType(WebDriverListenerException.class)
1111+
.isThrownBy(decorated::getWindowHandle)
1112+
.withMessageStartingWith("Exception executing listener method ");
1113+
}
1114+
10691115
@Test
10701116
void shouldSuppressExceptionInBeforeMethod() {
10711117
WebDriver driver = mock(WebDriver.class);
@@ -1082,6 +1128,30 @@ public void beforeGetWindowHandle(WebDriver driver) {
10821128
assertThatNoException().isThrownBy(decorated::getWindowHandle);
10831129
}
10841130

1131+
@Test
1132+
void shouldReThrowExceptionInBeforeMethod() {
1133+
WebDriver driver = mock(WebDriver.class);
1134+
WebDriverListener listener =
1135+
new WebDriverListener() {
1136+
1137+
@Override
1138+
public boolean throwsExceptions() {
1139+
return true;
1140+
}
1141+
1142+
@Override
1143+
public void beforeGetWindowHandle(WebDriver driver) {
1144+
throw new RuntimeException("listener");
1145+
}
1146+
};
1147+
1148+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1149+
1150+
assertThatExceptionOfType(WebDriverListenerException.class)
1151+
.isThrownBy(decorated::getWindowHandle)
1152+
.withMessageStartingWith("Exception executing listener method ");
1153+
}
1154+
10851155
@Test
10861156
void shouldSuppressExceptionInAfterAnyCall() {
10871157
WebDriver driver = mock(WebDriver.class);
@@ -1098,6 +1168,30 @@ public void afterAnyCall(Object target, Method method, Object[] args, Object res
10981168
assertThatNoException().isThrownBy(decorated::getWindowHandle);
10991169
}
11001170

1171+
@Test
1172+
void shouldReThrowExceptionInAfterAnyCall() {
1173+
WebDriver driver = mock(WebDriver.class);
1174+
WebDriverListener listener =
1175+
new WebDriverListener() {
1176+
1177+
@Override
1178+
public boolean throwsExceptions() {
1179+
return true;
1180+
}
1181+
1182+
@Override
1183+
public void afterAnyCall(Object target, Method method, Object[] args, Object result) {
1184+
throw new RuntimeException("listener");
1185+
}
1186+
};
1187+
1188+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1189+
1190+
assertThatExceptionOfType(WebDriverListenerException.class)
1191+
.isThrownBy(decorated::getWindowHandle)
1192+
.withMessage("afterAnyCall");
1193+
}
1194+
11011195
@Test
11021196
void shouldSuppressExceptionInAfterClassMethodCall() {
11031197
WebDriver driver = mock(WebDriver.class);
@@ -1115,6 +1209,31 @@ public void afterAnyWebDriverCall(
11151209
assertThatNoException().isThrownBy(decorated::getWindowHandle);
11161210
}
11171211

1212+
@Test
1213+
void shouldReThrowExceptionInAfterClassMethodCall() {
1214+
WebDriver driver = mock(WebDriver.class);
1215+
WebDriverListener listener =
1216+
new WebDriverListener() {
1217+
1218+
@Override
1219+
public boolean throwsExceptions() {
1220+
return true;
1221+
}
1222+
1223+
@Override
1224+
public void afterAnyWebDriverCall(
1225+
WebDriver driver, Method method, Object[] args, Object result) {
1226+
throw new RuntimeException("listener");
1227+
}
1228+
};
1229+
1230+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1231+
1232+
assertThatExceptionOfType(WebDriverListenerException.class)
1233+
.isThrownBy(decorated::getWindowHandle)
1234+
.withMessageStartingWith("Exception executing listener method ");
1235+
}
1236+
11181237
@Test
11191238
void shouldSuppressExceptionInAfterMethod() {
11201239
WebDriver driver = mock(WebDriver.class);
@@ -1131,6 +1250,30 @@ public void afterGetWindowHandle(WebDriver driver, String result) {
11311250
assertThatNoException().isThrownBy(decorated::getWindowHandle);
11321251
}
11331252

1253+
@Test
1254+
void shouldReThrowExceptionInAfterMethod() {
1255+
WebDriver driver = mock(WebDriver.class);
1256+
WebDriverListener listener =
1257+
new WebDriverListener() {
1258+
1259+
@Override
1260+
public boolean throwsExceptions() {
1261+
return true;
1262+
}
1263+
1264+
@Override
1265+
public void afterGetWindowHandle(WebDriver driver, String result) {
1266+
throw new RuntimeException("listener");
1267+
}
1268+
};
1269+
1270+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1271+
1272+
assertThatExceptionOfType(WebDriverListenerException.class)
1273+
.isThrownBy(decorated::getWindowHandle)
1274+
.withMessageStartingWith("Exception executing listener method ");
1275+
}
1276+
11341277
@Test
11351278
void shouldSuppressExceptionInOnError() {
11361279
WebDriver driver = mock(WebDriver.class);

0 commit comments

Comments
 (0)