Java tutorial
// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package org.openqa.selenium.support.events; import org.openqa.selenium.Alert; import org.openqa.selenium.Beta; import org.openqa.selenium.By; import org.openqa.selenium.Capabilities; import org.openqa.selenium.Cookie; import org.openqa.selenium.Dimension; import org.openqa.selenium.HasCapabilities; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.OutputType; import org.openqa.selenium.Point; import org.openqa.selenium.Rectangle; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; import org.openqa.selenium.WindowType; import org.openqa.selenium.WrapsDriver; import org.openqa.selenium.WrapsElement; import org.openqa.selenium.interactions.Coordinates; import org.openqa.selenium.interactions.HasInputDevices; import org.openqa.selenium.interactions.HasTouchScreen; import org.openqa.selenium.interactions.Interactive; import org.openqa.selenium.interactions.Keyboard; import org.openqa.selenium.interactions.Locatable; import org.openqa.selenium.interactions.Mouse; import org.openqa.selenium.interactions.Sequence; import org.openqa.selenium.interactions.TouchScreen; import org.openqa.selenium.logging.Logs; import org.openqa.selenium.support.events.internal.EventFiringKeyboard; import org.openqa.selenium.support.events.internal.EventFiringMouse; import org.openqa.selenium.support.events.internal.EventFiringTouch; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * A wrapper around an arbitrary {@link WebDriver} instance which supports registering of a * {@link WebDriverEventListener}, e.g. for logging purposes. */ public class EventFiringWebDriver implements WebDriver, JavascriptExecutor, TakesScreenshot, WrapsDriver, HasInputDevices, HasTouchScreen, Interactive, HasCapabilities { private final WebDriver driver; private final List<WebDriverEventListener> eventListeners = new ArrayList<>(); private final WebDriverEventListener dispatcher = (WebDriverEventListener) Proxy.newProxyInstance( WebDriverEventListener.class.getClassLoader(), new Class[] { WebDriverEventListener.class }, (proxy, method, args) -> { try { for (WebDriverEventListener eventListener : eventListeners) { method.invoke(eventListener, args); } return null; } catch (InvocationTargetException e) { throw e.getTargetException(); } }); public EventFiringWebDriver(final WebDriver driver) { Class<?>[] allInterfaces = extractInterfaces(driver); this.driver = (WebDriver) Proxy.newProxyInstance(WebDriverEventListener.class.getClassLoader(), allInterfaces, (proxy, method, args) -> { if ("getWrappedDriver".equals(method.getName())) { return driver; } try { return method.invoke(driver, args); } catch (InvocationTargetException e) { dispatcher.onException(e.getTargetException(), driver); throw e.getTargetException(); } }); } private Class<?>[] extractInterfaces(Object object) { Set<Class<?>> allInterfaces = new HashSet<>(); allInterfaces.add(WrapsDriver.class); if (object instanceof WebElement) { allInterfaces.add(WrapsElement.class); } extractInterfaces(allInterfaces, object.getClass()); return allInterfaces.toArray(new Class<?>[allInterfaces.size()]); } private void extractInterfaces(Set<Class<?>> addTo, Class<?> clazz) { if (Object.class.equals(clazz)) { return; // Done } Class<?>[] classes = clazz.getInterfaces(); addTo.addAll(Arrays.asList(classes)); extractInterfaces(addTo, clazz.getSuperclass()); } /** * @param eventListener the event listener to register * @return this for method chaining. */ public EventFiringWebDriver register(WebDriverEventListener eventListener) { eventListeners.add(eventListener); return this; } /** * @param eventListener the event listener to unregister * @return this for method chaining. */ public EventFiringWebDriver unregister(WebDriverEventListener eventListener) { eventListeners.remove(eventListener); return this; } @Override public WebDriver getWrappedDriver() { if (driver instanceof WrapsDriver) { return ((WrapsDriver) driver).getWrappedDriver(); } return driver; } @Override public void get(String url) { dispatcher.beforeNavigateTo(url, driver); driver.get(url); dispatcher.afterNavigateTo(url, driver); } @Override public String getCurrentUrl() { return driver.getCurrentUrl(); } @Override public String getTitle() { return driver.getTitle(); } @Override public List<WebElement> findElements(By by) { dispatcher.beforeFindBy(by, null, driver); List<WebElement> temp = driver.findElements(by); dispatcher.afterFindBy(by, null, driver); List<WebElement> result = new ArrayList<>(temp.size()); for (WebElement element : temp) { result.add(createWebElement(element)); } return result; } @Override public WebElement findElement(By by) { dispatcher.beforeFindBy(by, null, driver); WebElement temp = driver.findElement(by); dispatcher.afterFindBy(by, temp, driver); return createWebElement(temp); } @Override public String getPageSource() { return driver.getPageSource(); } @Override public void close() { driver.close(); } @Override public void quit() { driver.quit(); } @Override public Set<String> getWindowHandles() { return driver.getWindowHandles(); } @Override public String getWindowHandle() { return driver.getWindowHandle(); } @Override public Object executeScript(String script, Object... args) { if (driver instanceof JavascriptExecutor) { dispatcher.beforeScript(script, driver); Object[] usedArgs = unpackWrappedArgs(args); Object result = ((JavascriptExecutor) driver).executeScript(script, usedArgs); dispatcher.afterScript(script, driver); return wrapResult(result); } throw new UnsupportedOperationException("Underlying driver instance does not support executing javascript"); } @Override public Object executeAsyncScript(String script, Object... args) { if (driver instanceof JavascriptExecutor) { dispatcher.beforeScript(script, driver); Object[] usedArgs = unpackWrappedArgs(args); Object result = ((JavascriptExecutor) driver).executeAsyncScript(script, usedArgs); dispatcher.afterScript(script, driver); return result; } throw new UnsupportedOperationException("Underlying driver instance does not support executing javascript"); } private Object[] unpackWrappedArgs(Object... args) { // Walk the args: the various drivers expect unpacked versions of the elements Object[] usedArgs = new Object[args.length]; for (int i = 0; i < args.length; i++) { usedArgs[i] = unpackWrappedElement(args[i]); } return usedArgs; } private Object unpackWrappedElement(Object arg) { if (arg instanceof List<?>) { List<?> aList = (List<?>) arg; List<Object> toReturn = new ArrayList<>(); for (Object anAList : aList) { toReturn.add(unpackWrappedElement(anAList)); } return toReturn; } else if (arg instanceof Map<?, ?>) { Map<?, ?> aMap = (Map<?, ?>) arg; Map<Object, Object> toReturn = new HashMap<>(); for (Object key : aMap.keySet()) { toReturn.put(key, unpackWrappedElement(aMap.get(key))); } return toReturn; } else if (arg instanceof EventFiringWebElement) { return ((EventFiringWebElement) arg).getWrappedElement(); } else { return arg; } } private Object wrapResult(Object result) { if (result instanceof WebElement) { return new EventFiringWebElement((WebElement) result); } if (result instanceof List) { return ((List<?>) result).stream().map(this::wrapResult).collect(Collectors.toList()); } if (result instanceof Map) { return ((Map<?, ?>) result).entrySet().stream().collect(HashMap::new, (m, e) -> m.put(e.getKey(), e.getValue()), Map::putAll); } return result; } @Override public <X> X getScreenshotAs(OutputType<X> target) throws WebDriverException { if (driver instanceof TakesScreenshot) { dispatcher.beforeGetScreenshotAs(target); X screenshot = ((TakesScreenshot) driver).getScreenshotAs(target); dispatcher.afterGetScreenshotAs(target, screenshot); return screenshot; } throw new UnsupportedOperationException("Underlying driver instance does not support taking screenshots"); } @Override public TargetLocator switchTo() { return new EventFiringTargetLocator(driver.switchTo()); } @Override public Navigation navigate() { return new EventFiringNavigation(driver.navigate()); } @Override public Options manage() { return new EventFiringOptions(driver.manage()); } private WebElement createWebElement(WebElement from) { return new EventFiringWebElement(from); } @Override public Keyboard getKeyboard() { if (driver instanceof HasInputDevices) { return new EventFiringKeyboard(driver, dispatcher); } throw new UnsupportedOperationException( "Underlying driver does not implement advanced" + " user interactions yet."); } @Override public Mouse getMouse() { if (driver instanceof HasInputDevices) { return new EventFiringMouse(driver, dispatcher); } throw new UnsupportedOperationException( "Underlying driver does not implement advanced" + " user interactions yet."); } @Override public TouchScreen getTouch() { if (driver instanceof HasTouchScreen) { return new EventFiringTouch(driver, dispatcher); } throw new UnsupportedOperationException( "Underlying driver does not implement advanced" + " user interactions yet."); } @Override public void perform(Collection<Sequence> actions) { if (driver instanceof Interactive) { ((Interactive) driver).perform(actions); return; } throw new UnsupportedOperationException( "Underlying driver does not implement advanced" + " user interactions yet."); } @Override public void resetInputState() { if (driver instanceof Interactive) { ((Interactive) driver).resetInputState(); return; } throw new UnsupportedOperationException( "Underlying driver does not implement advanced" + " user interactions yet."); } @Override public Capabilities getCapabilities() { if (driver instanceof HasCapabilities) { return ((HasCapabilities) driver).getCapabilities(); } throw new UnsupportedOperationException("Underlying driver does not implement getting capabilities yet."); } private class EventFiringWebElement implements WebElement, WrapsElement, WrapsDriver, org.openqa.selenium.interactions.Locatable { private final WebElement element; private final WebElement underlyingElement; private EventFiringWebElement(final WebElement element) { this.element = (WebElement) Proxy.newProxyInstance(WebDriverEventListener.class.getClassLoader(), extractInterfaces(element), (proxy, method, args) -> { if (method.getName().equals("getWrappedElement")) { return element; } try { return method.invoke(element, args); } catch (InvocationTargetException e) { dispatcher.onException(e.getTargetException(), driver); throw e.getTargetException(); } }); this.underlyingElement = element; } @Override public void click() { dispatcher.beforeClickOn(element, driver); element.click(); dispatcher.afterClickOn(element, driver); } @Override public void submit() { element.submit(); } @Override public void sendKeys(CharSequence... keysToSend) { dispatcher.beforeChangeValueOf(element, driver, keysToSend); element.sendKeys(keysToSend); dispatcher.afterChangeValueOf(element, driver, keysToSend); } @Override public void clear() { dispatcher.beforeChangeValueOf(element, driver, null); element.clear(); dispatcher.afterChangeValueOf(element, driver, null); } @Override public String getTagName() { return element.getTagName(); } @Override public String getAttribute(String name) { return element.getAttribute(name); } @Override public boolean isSelected() { return element.isSelected(); } @Override public boolean isEnabled() { return element.isEnabled(); } @Override public String getText() { dispatcher.beforeGetText(element, driver); String text = element.getText(); dispatcher.afterGetText(element, driver, text); return text; } @Override public boolean isDisplayed() { return element.isDisplayed(); } @Override public Point getLocation() { return element.getLocation(); } @Override public Dimension getSize() { return element.getSize(); } @Override public Rectangle getRect() { return element.getRect(); } @Override public String getCssValue(String propertyName) { return element.getCssValue(propertyName); } @Override public WebElement findElement(By by) { dispatcher.beforeFindBy(by, element, driver); WebElement temp = element.findElement(by); dispatcher.afterFindBy(by, element, driver); return createWebElement(temp); } @Override public List<WebElement> findElements(By by) { dispatcher.beforeFindBy(by, element, driver); List<WebElement> temp = element.findElements(by); dispatcher.afterFindBy(by, element, driver); List<WebElement> result = new ArrayList<>(temp.size()); for (WebElement element : temp) { result.add(createWebElement(element)); } return result; } @Override public WebElement getWrappedElement() { return underlyingElement; } @Override public boolean equals(Object obj) { if (!(obj instanceof WebElement)) { return false; } WebElement other = (WebElement) obj; if (other instanceof WrapsElement) { other = ((WrapsElement) other).getWrappedElement(); } return underlyingElement.equals(other); } @Override public int hashCode() { return underlyingElement.hashCode(); } @Override public String toString() { return underlyingElement.toString(); } @Override public WebDriver getWrappedDriver() { return driver; } @Override public Coordinates getCoordinates() { return ((Locatable) underlyingElement).getCoordinates(); } @Override public <X> X getScreenshotAs(OutputType<X> outputType) throws WebDriverException { return element.getScreenshotAs(outputType); } } private class EventFiringNavigation implements Navigation { private final WebDriver.Navigation navigation; EventFiringNavigation(Navigation navigation) { this.navigation = navigation; } @Override public void to(String url) { dispatcher.beforeNavigateTo(url, driver); navigation.to(url); dispatcher.afterNavigateTo(url, driver); } @Override public void to(URL url) { to(String.valueOf(url)); } @Override public void back() { dispatcher.beforeNavigateBack(driver); navigation.back(); dispatcher.afterNavigateBack(driver); } @Override public void forward() { dispatcher.beforeNavigateForward(driver); navigation.forward(); dispatcher.afterNavigateForward(driver); } @Override public void refresh() { dispatcher.beforeNavigateRefresh(driver); navigation.refresh(); dispatcher.afterNavigateRefresh(driver); } } private class EventFiringOptions implements Options { private Options options; private EventFiringOptions(Options options) { this.options = options; } @Override public Logs logs() { return options.logs(); } @Override public void addCookie(Cookie cookie) { options.addCookie(cookie); } @Override public void deleteCookieNamed(String name) { options.deleteCookieNamed(name); } @Override public void deleteCookie(Cookie cookie) { options.deleteCookie(cookie); } @Override public void deleteAllCookies() { options.deleteAllCookies(); } @Override public Set<Cookie> getCookies() { return options.getCookies(); } @Override public Cookie getCookieNamed(String name) { return options.getCookieNamed(name); } @Override public Timeouts timeouts() { return new EventFiringTimeouts(options.timeouts()); } @Override public ImeHandler ime() { return options.ime(); } @Override @Beta public Window window() { return new EventFiringWindow(options.window()); } } private class EventFiringTimeouts implements Timeouts { private final Timeouts timeouts; EventFiringTimeouts(Timeouts timeouts) { this.timeouts = timeouts; } @Override public Timeouts implicitlyWait(long time, TimeUnit unit) { timeouts.implicitlyWait(time, unit); return this; } @Override public Timeouts setScriptTimeout(long time, TimeUnit unit) { timeouts.setScriptTimeout(time, unit); return this; } @Override public Timeouts pageLoadTimeout(long time, TimeUnit unit) { timeouts.pageLoadTimeout(time, unit); return this; } } private class EventFiringTargetLocator implements TargetLocator { private TargetLocator targetLocator; private EventFiringTargetLocator(TargetLocator targetLocator) { this.targetLocator = targetLocator; } @Override public WebDriver frame(int frameIndex) { return targetLocator.frame(frameIndex); } @Override public WebDriver frame(String frameName) { return targetLocator.frame(frameName); } @Override public WebDriver frame(WebElement frameElement) { return targetLocator.frame(frameElement); } @Override public WebDriver parentFrame() { return targetLocator.parentFrame(); } @Override public WebDriver window(String windowName) { dispatcher.beforeSwitchToWindow(windowName, driver); WebDriver driverToReturn = targetLocator.window(windowName); dispatcher.afterSwitchToWindow(windowName, driver); return driverToReturn; } @Override public WebDriver newWindow(WindowType typeHint) { dispatcher.beforeSwitchToWindow(null, driver); WebDriver driverToReturn = targetLocator.newWindow(typeHint); dispatcher.afterSwitchToWindow(null, driver); return driverToReturn; } @Override public WebDriver defaultContent() { return targetLocator.defaultContent(); } @Override public WebElement activeElement() { return targetLocator.activeElement(); } @Override public Alert alert() { return new EventFiringAlert(this.targetLocator.alert()); } } @Beta private class EventFiringWindow implements Window { private final Window window; EventFiringWindow(Window window) { this.window = window; } @Override public void setSize(Dimension targetSize) { window.setSize(targetSize); } @Override public void setPosition(Point targetLocation) { window.setPosition(targetLocation); } @Override public Dimension getSize() { return window.getSize(); } @Override public Point getPosition() { return window.getPosition(); } @Override public void maximize() { window.maximize(); } @Override public void fullscreen() { window.fullscreen(); } } private class EventFiringAlert implements Alert { private final Alert alert; private EventFiringAlert(Alert alert) { this.alert = alert; } @Override public void dismiss() { dispatcher.beforeAlertDismiss(driver); alert.dismiss(); dispatcher.afterAlertDismiss(driver); } @Override public void accept() { dispatcher.beforeAlertAccept(driver); alert.accept(); dispatcher.afterAlertAccept(driver); } @Override public String getText() { return alert.getText(); } @Override public void sendKeys(String keysToSend) { alert.sendKeys(keysToSend); } } }