17
17
18
18
package org .openqa .selenium .support .events ;
19
19
20
- import com .google .common .base .Throwables ;
21
20
import com .google .common .primitives .Primitives ;
22
21
23
22
import org .openqa .selenium .Alert ;
31
30
import java .lang .reflect .Method ;
32
31
import java .util .Arrays ;
33
32
import java .util .List ;
33
+ import java .util .logging .Level ;
34
+ import java .util .logging .Logger ;
34
35
36
+ /**
37
+ * This decorator creates a wrapper around an arbitrary {@link WebDriver} instance that notifies
38
+ * registered listeners about events happening in this WebDriver instance and related objects
39
+ * ({@link WebElement}}s found by this driver, {@link Alert}s,
40
+ * {@link org.openqa.selenium.WebDriver.Window}s etc).
41
+ * <p>
42
+ * Listeners should implement {@link WebDriverListener} interface. It supports three types of events:
43
+ * <ul>
44
+ * <li>"before"-event: a method is about to be called;</li>
45
+ * <li>"after"-event: a method was called successfully and returned some result;</li>
46
+ * <li>"error"-event: a method was called and thrown an exception.</li>
47
+ * </ul>
48
+ * To use this decorator you have to prepare a listener, create a decorator using this listener,
49
+ * decorate the original WebDriver instance with this decorator and use the new WebDriver
50
+ * instance created by decorator instead of the original one:
51
+ * <code>
52
+ * WebDriver original = new FirefoxDriver();
53
+ * WebDriverListener listener = new MyListener();
54
+ * WebDriver decorated = new EventFiringDecorator(listener).decorate(original);
55
+ * decorated.get("http://example.com/");
56
+ * WebElement header = decorated.findElement(By.tagName("h1"));
57
+ * String headerText = header.getText();
58
+ * </code>
59
+ * <p>
60
+ * The instance of WebDriver created by the decorator implements all the same interfaces as
61
+ * the original driver.
62
+ * <p>
63
+ * A listener can subscribe to "specific" or "generic" events (or both). A "specific" event
64
+ * correspond to a single specific method, a "generic" event correspond to any method called in
65
+ * a class or in any class.
66
+ * <p>
67
+ * To subscribe to a "specific" event a listener should implement a method with a name build after
68
+ * the target method to be watched. The listener methods for "before"-events receive the parameters
69
+ * passed to the decorated method. The listener methods for "after"-events receive the parameters
70
+ * passed to the decorated method as well as the result returned by this method.
71
+ * <code>
72
+ * WebDriverListener listener = new WebDriverListener() {
73
+ * @Override
74
+ * public void beforeGet(WebDriver driver, String url) {
75
+ * logger.log("About to open a page %s", url);
76
+ * }
77
+ * @Override
78
+ * public void afterGetText(String result, WebElement element) {
79
+ * logger.log("Element %s has text '%s'", element, result);
80
+ * }
81
+ * };
82
+ * </code>
83
+ * <p>
84
+ * To subscribe to a "generic" event a listener should implement a method with a name build after
85
+ * the class to be watched:
86
+ * <code>
87
+ * WebDriverListener listener = new WebDriverListener() {
88
+ * @Override
89
+ * public void beforeAnyWebElementCall(WebElement element, Method method, Object[] args) {
90
+ * logger.log("About to call a method %s in element %s with parameters %s",
91
+ * method, element, args);
92
+ * }
93
+ * @Override
94
+ * public void afterAnyWebElementCall(WebElement element, Method method, Object result, Object[] args) {
95
+ * logger.log("Method %s called in element %s with parameters %s returned %s",
96
+ * method, element, args, result);
97
+ * }
98
+ * };
99
+ * </code>
100
+ * <p>
101
+ * There are also listener methods for "super-generic" events:
102
+ * <code>
103
+ * WebDriverListener listener = new WebDriverListener() {
104
+ * @Override
105
+ * public void beforeAnyCall(Object target, Method method, Object[] args) {
106
+ * logger.log("About to call a method %s in %s with parameters %s",
107
+ * method, target, args);
108
+ * }
109
+ * @Override
110
+ * public void afterAnyCall(Object target, Method method, Object result, Object[] args) {
111
+ * logger.log("Method %s called in %s with parameters %s returned %s",
112
+ * method, target, args, result);
113
+ * }
114
+ * };
115
+ * </code>
116
+ * <p>
117
+ * A listener can subscribe to both "specific" and "generic" events at the same time. In this case
118
+ * "before"-events are fired in order from the most generic to the most specific,
119
+ * and "after"-events are generated in the opposite order, for example:
120
+ * <code>
121
+ * beforeAnyCall
122
+ * beforeAnyWebDriverCall
123
+ * beforeGet
124
+ * // the actual call to the decorated method here
125
+ * afterGet
126
+ * afterAnyWebDriverCall
127
+ * afterAnyCall
128
+ * </code>
129
+ * <p>
130
+ * One of the most obvious use of this decorator is logging. But it can be used to modify behavior
131
+ * of the original driver to some extent because listener methods are executed in the same thread
132
+ * as the original driver methods.
133
+ * <p>
134
+ * For example, a listener can be used to slow down execution for demonstration purposes, just
135
+ * make a listener that adds a pause before some operations:
136
+ * <code>
137
+ * WebDriverListener listener = new WebDriverListener() {
138
+ * @Override
139
+ * public void beforeClick(WebElement element) {
140
+ * try {
141
+ * Thread.sleep(3000);
142
+ * } catch (InterruptedException e) {
143
+ * Thread.currentThread().interrupt();
144
+ * }
145
+ * }
146
+ * };
147
+ * </code>
148
+ * <p>
149
+ * Just be careful to not block the current thread in a listener method!
150
+ * <p>
151
+ * Actually, listeners can't affect driver behavior too much. They can't throw any exceptions
152
+ * (they can, but the decorator suppresses these exceptions), can't prevent execution of
153
+ * the decorated methods, can't modify parameters and results of the methods.
154
+ * <p>
155
+ * Decorators that modify the behaviour of the underlying drivers should be implemented by
156
+ * extending {@link WebDriverDecorator}, not by creating sophisticated listeners.
157
+ */
35
158
@ Beta
36
159
public class EventFiringDecorator extends WebDriverDecorator {
37
160
161
+ private static final Logger logger = Logger .getLogger (EventFiringDecorator .class .getName ());
162
+
38
163
private final List <WebDriverListener > listeners ;
39
164
40
165
public EventFiringDecorator (WebDriverListener ... listeners ) {
@@ -55,26 +180,41 @@ public void afterCallGlobal(Decorated<?> target, Method method, Object result, O
55
180
56
181
@ Override
57
182
public Object onErrorGlobal (Decorated <?> target , Method method , InvocationTargetException e , Object [] args ) throws Throwable {
58
- listeners .forEach (listener -> listener .onError (target .getOriginal (), method , e , args ));
183
+ listeners .forEach (listener -> {
184
+ try {
185
+ listener .onError (target .getOriginal (), method , e , args );
186
+ } catch (Throwable t ) {
187
+ logger .log (Level .WARNING , t .getMessage (), t );
188
+ }
189
+ });
59
190
return super .onErrorGlobal (target , method , e , args );
60
191
}
61
192
62
193
private void fireBeforeEvents (WebDriverListener listener , Decorated <?> target , Method method , Object [] args ) {
63
- listener .beforeAnyCall (target .getOriginal (), method , args );
64
- if (target .getOriginal () instanceof WebDriver ) {
65
- listener .beforeAnyWebDriverCall ((WebDriver ) target .getOriginal (), method , args );
66
- } else if (target .getOriginal () instanceof WebElement ) {
67
- listener .beforeAnyWebElementCall ((WebElement ) target .getOriginal (), method , args );
68
- } else if (target .getOriginal () instanceof WebDriver .Navigation ) {
69
- listener .beforeAnyNavigationCall ((WebDriver .Navigation ) target .getOriginal (), method , args );
70
- } else if (target .getOriginal () instanceof Alert ) {
71
- listener .beforeAnyAlertCall ((Alert ) target .getOriginal (), method , args );
72
- } else if (target .getOriginal () instanceof WebDriver .Options ) {
73
- listener .beforeAnyOptionsCall ((WebDriver .Options ) target .getOriginal (), method , args );
74
- } else if (target .getOriginal () instanceof WebDriver .Timeouts ) {
75
- listener .beforeAnyTimeoutsCall ((WebDriver .Timeouts ) target .getOriginal (), method , args );
76
- } else if (target .getOriginal () instanceof WebDriver .Window ) {
77
- listener .beforeAnyWindowCall ((WebDriver .Window ) target .getOriginal (), method , args );
194
+ try {
195
+ listener .beforeAnyCall (target .getOriginal (), method , args );
196
+ } catch (Throwable t ) {
197
+ logger .log (Level .WARNING , t .getMessage (), t );
198
+ }
199
+
200
+ try {
201
+ if (target .getOriginal () instanceof WebDriver ) {
202
+ listener .beforeAnyWebDriverCall ((WebDriver ) target .getOriginal (), method , args );
203
+ } else if (target .getOriginal () instanceof WebElement ) {
204
+ listener .beforeAnyWebElementCall ((WebElement ) target .getOriginal (), method , args );
205
+ } else if (target .getOriginal () instanceof WebDriver .Navigation ) {
206
+ listener .beforeAnyNavigationCall ((WebDriver .Navigation ) target .getOriginal (), method , args );
207
+ } else if (target .getOriginal () instanceof Alert ) {
208
+ listener .beforeAnyAlertCall ((Alert ) target .getOriginal (), method , args );
209
+ } else if (target .getOriginal () instanceof WebDriver .Options ) {
210
+ listener .beforeAnyOptionsCall ((WebDriver .Options ) target .getOriginal (), method , args );
211
+ } else if (target .getOriginal () instanceof WebDriver .Timeouts ) {
212
+ listener .beforeAnyTimeoutsCall ((WebDriver .Timeouts ) target .getOriginal (), method , args );
213
+ } else if (target .getOriginal () instanceof WebDriver .Window ) {
214
+ listener .beforeAnyWindowCall ((WebDriver .Window ) target .getOriginal (), method , args );
215
+ }
216
+ } catch (Throwable t ) {
217
+ logger .log (Level .WARNING , t .getMessage (), t );
78
218
}
79
219
80
220
String methodName = createEventMethodName ("before" , method .getName ());
@@ -93,23 +233,6 @@ private void fireBeforeEvents(WebDriverListener listener, Decorated<?> target, M
93
233
}
94
234
95
235
private void fireAfterEvents (WebDriverListener listener , Decorated <?> target , Method method , Object res , Object [] args ) {
96
- listener .afterAnyCall (target .getOriginal (), method , res , args );
97
- if (target .getOriginal () instanceof WebDriver ) {
98
- listener .afterAnyWebDriverCall ((WebDriver ) target .getOriginal (), method , res , args );
99
- } else if (target .getOriginal () instanceof WebElement ) {
100
- listener .afterAnyWebElementCall ((WebElement ) target .getOriginal (), method , res , args );
101
- } else if (target .getOriginal () instanceof WebDriver .Navigation ) {
102
- listener .afterAnyNavigationCall ((WebDriver .Navigation ) target .getOriginal (), method , res , args );
103
- } else if (target .getOriginal () instanceof Alert ) {
104
- listener .afterAnyAlertCall ((Alert ) target .getOriginal (), method , res , args );
105
- } else if (target .getOriginal () instanceof WebDriver .Options ) {
106
- listener .afterAnyOptionsCall ((WebDriver .Options ) target .getOriginal (), method , res , args );
107
- } else if (target .getOriginal () instanceof WebDriver .Timeouts ) {
108
- listener .afterAnyTimeoutsCall ((WebDriver .Timeouts ) target .getOriginal (), method , res , args );
109
- } else if (target .getOriginal () instanceof WebDriver .Window ) {
110
- listener .afterAnyWindowCall ((WebDriver .Window ) target .getOriginal (), method , res , args );
111
- }
112
-
113
236
String methodName = createEventMethodName ("after" , method .getName ());
114
237
115
238
boolean isVoid = method .getReturnType () == Void .TYPE
@@ -130,6 +253,32 @@ private void fireAfterEvents(WebDriverListener listener, Decorated<?> target, Me
130
253
if (m != null ) {
131
254
callListenerMethod (m , listener , args2 );
132
255
}
256
+
257
+ try {
258
+ if (target .getOriginal () instanceof WebDriver ) {
259
+ listener .afterAnyWebDriverCall ((WebDriver ) target .getOriginal (), method , res , args );
260
+ } else if (target .getOriginal () instanceof WebElement ) {
261
+ listener .afterAnyWebElementCall ((WebElement ) target .getOriginal (), method , res , args );
262
+ } else if (target .getOriginal () instanceof WebDriver .Navigation ) {
263
+ listener .afterAnyNavigationCall ((WebDriver .Navigation ) target .getOriginal (), method , res , args );
264
+ } else if (target .getOriginal () instanceof Alert ) {
265
+ listener .afterAnyAlertCall ((Alert ) target .getOriginal (), method , res , args );
266
+ } else if (target .getOriginal () instanceof WebDriver .Options ) {
267
+ listener .afterAnyOptionsCall ((WebDriver .Options ) target .getOriginal (), method , res , args );
268
+ } else if (target .getOriginal () instanceof WebDriver .Timeouts ) {
269
+ listener .afterAnyTimeoutsCall ((WebDriver .Timeouts ) target .getOriginal (), method , res , args );
270
+ } else if (target .getOriginal () instanceof WebDriver .Window ) {
271
+ listener .afterAnyWindowCall ((WebDriver .Window ) target .getOriginal (), method , res , args );
272
+ }
273
+ } catch (Throwable t ) {
274
+ logger .log (Level .WARNING , t .getMessage (), t );
275
+ }
276
+
277
+ try {
278
+ listener .afterAnyCall (target .getOriginal (), method , res , args );
279
+ } catch (Throwable t ) {
280
+ logger .log (Level .WARNING , t .getMessage (), t );
281
+ }
133
282
}
134
283
135
284
private String createEventMethodName (String prefix , String originalMethodName ) {
@@ -151,7 +300,7 @@ private boolean parametersMatch(Method m, Object[] args) {
151
300
return false ;
152
301
}
153
302
for (int i = 0 ; i < params .length ; i ++) {
154
- if (! Primitives .wrap (params [i ]).isAssignableFrom (args [i ].getClass ())) {
303
+ if (args [ i ] != null && ! Primitives .wrap (params [i ]).isAssignableFrom (args [i ].getClass ())) {
155
304
return false ;
156
305
}
157
306
}
@@ -161,11 +310,8 @@ private boolean parametersMatch(Method m, Object[] args) {
161
310
private void callListenerMethod (Method m , WebDriverListener listener , Object [] args ) {
162
311
try {
163
312
m .invoke (listener , args );
164
- } catch (IllegalAccessException e ) {
165
- throw new RuntimeException (e );
166
- } catch (InvocationTargetException e ) {
167
- Throwables .throwIfUnchecked (e .getCause ());
168
- throw new RuntimeException (e .getCause ());
313
+ } catch (Throwable t ) {
314
+ logger .log (Level .WARNING , t .getMessage (), t );
169
315
}
170
316
}
171
317
}
0 commit comments