Skip to content

Commit b189814

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 b189814

File tree

4 files changed

+212
-6
lines changed

4 files changed

+212
-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(method, 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(method, 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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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(Method method, Throwable cause) {
26+
super(
27+
"Exception executing listener method "
28+
+ method.getDeclaringClass().getSimpleName()
29+
+ "#"
30+
+ method.getName()
31+
+ " with parameter types "
32+
+ Arrays.toString(
33+
Arrays.stream(method.getParameterTypes()).map(Class::getSimpleName).toArray()),
34+
cause);
35+
}
36+
}

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

Lines changed: 140 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,29 @@ 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+
}
1073+
10531074
@Test
10541075
void shouldSuppressExceptionInBeforeClassMethodCall() {
10551076
WebDriver driver = mock(WebDriver.class);
@@ -1066,6 +1087,29 @@ public void beforeAnyWebDriverCall(WebDriver driver, Method method, Object[] arg
10661087
assertThatNoException().isThrownBy(decorated::getWindowHandle);
10671088
}
10681089

1090+
@Test
1091+
void shouldReThrowExceptionInBeforeClassMethodCall() {
1092+
WebDriver driver = mock(WebDriver.class);
1093+
WebDriverListener listener =
1094+
new WebDriverListener() {
1095+
1096+
@Override
1097+
public boolean throwsExceptions() {
1098+
return true;
1099+
}
1100+
1101+
@Override
1102+
public void beforeAnyWebDriverCall(WebDriver driver, Method method, Object[] args) {
1103+
throw new RuntimeException("listener");
1104+
}
1105+
};
1106+
1107+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1108+
1109+
assertThatExceptionOfType(WebDriverListenerException.class)
1110+
.isThrownBy(decorated::getWindowHandle);
1111+
}
1112+
10691113
@Test
10701114
void shouldSuppressExceptionInBeforeMethod() {
10711115
WebDriver driver = mock(WebDriver.class);
@@ -1082,6 +1126,29 @@ public void beforeGetWindowHandle(WebDriver driver) {
10821126
assertThatNoException().isThrownBy(decorated::getWindowHandle);
10831127
}
10841128

1129+
@Test
1130+
void shouldReThrowExceptionInBeforeMethod() {
1131+
WebDriver driver = mock(WebDriver.class);
1132+
WebDriverListener listener =
1133+
new WebDriverListener() {
1134+
1135+
@Override
1136+
public boolean throwsExceptions() {
1137+
return true;
1138+
}
1139+
1140+
@Override
1141+
public void beforeGetWindowHandle(WebDriver driver) {
1142+
throw new RuntimeException("listener");
1143+
}
1144+
};
1145+
1146+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1147+
1148+
assertThatExceptionOfType(WebDriverListenerException.class)
1149+
.isThrownBy(decorated::getWindowHandle);
1150+
}
1151+
10851152
@Test
10861153
void shouldSuppressExceptionInAfterAnyCall() {
10871154
WebDriver driver = mock(WebDriver.class);
@@ -1098,6 +1165,29 @@ public void afterAnyCall(Object target, Method method, Object[] args, Object res
10981165
assertThatNoException().isThrownBy(decorated::getWindowHandle);
10991166
}
11001167

1168+
@Test
1169+
void shouldReThrowExceptionInAfterAnyCall() {
1170+
WebDriver driver = mock(WebDriver.class);
1171+
WebDriverListener listener =
1172+
new WebDriverListener() {
1173+
1174+
@Override
1175+
public boolean throwsExceptions() {
1176+
return true;
1177+
}
1178+
1179+
@Override
1180+
public void afterAnyCall(Object target, Method method, Object[] args, Object result) {
1181+
throw new RuntimeException("listener");
1182+
}
1183+
};
1184+
1185+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1186+
1187+
assertThatExceptionOfType(WebDriverListenerException.class)
1188+
.isThrownBy(decorated::getWindowHandle);
1189+
}
1190+
11011191
@Test
11021192
void shouldSuppressExceptionInAfterClassMethodCall() {
11031193
WebDriver driver = mock(WebDriver.class);
@@ -1115,6 +1205,30 @@ public void afterAnyWebDriverCall(
11151205
assertThatNoException().isThrownBy(decorated::getWindowHandle);
11161206
}
11171207

1208+
@Test
1209+
void shouldReThrowExceptionInAfterClassMethodCall() {
1210+
WebDriver driver = mock(WebDriver.class);
1211+
WebDriverListener listener =
1212+
new WebDriverListener() {
1213+
1214+
@Override
1215+
public boolean throwsExceptions() {
1216+
return true;
1217+
}
1218+
1219+
@Override
1220+
public void afterAnyWebDriverCall(
1221+
WebDriver driver, Method method, Object[] args, Object result) {
1222+
throw new RuntimeException("listener");
1223+
}
1224+
};
1225+
1226+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1227+
1228+
assertThatExceptionOfType(WebDriverListenerException.class)
1229+
.isThrownBy(decorated::getWindowHandle);
1230+
}
1231+
11181232
@Test
11191233
void shouldSuppressExceptionInAfterMethod() {
11201234
WebDriver driver = mock(WebDriver.class);
@@ -1131,6 +1245,29 @@ public void afterGetWindowHandle(WebDriver driver, String result) {
11311245
assertThatNoException().isThrownBy(decorated::getWindowHandle);
11321246
}
11331247

1248+
@Test
1249+
void shouldReThrowExceptionInAfterMethod() {
1250+
WebDriver driver = mock(WebDriver.class);
1251+
WebDriverListener listener =
1252+
new WebDriverListener() {
1253+
1254+
@Override
1255+
public boolean throwsExceptions() {
1256+
return true;
1257+
}
1258+
1259+
@Override
1260+
public void afterGetWindowHandle(WebDriver driver, String result) {
1261+
throw new RuntimeException("listener");
1262+
}
1263+
};
1264+
1265+
WebDriver decorated = new EventFiringDecorator<>(listener).decorate(driver);
1266+
1267+
assertThatExceptionOfType(WebDriverListenerException.class)
1268+
.isThrownBy(decorated::getWindowHandle);
1269+
}
1270+
11341271
@Test
11351272
void shouldSuppressExceptionInOnError() {
11361273
WebDriver driver = mock(WebDriver.class);

0 commit comments

Comments
 (0)