code: // 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 static org.assertj.core.api.Assertions.assertThat;
  import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
  import static org.mockito.ArgumentMatchers.any;
  import static org.mockito.Mockito.doReturn;
  import static org.mockito.Mockito.eq;
  import static org.mockito.Mockito.mock;
  import static org.mockito.Mockito.times;
  import static org.mockito.Mockito.verify;
  import static org.mockito.Mockito.verifyNoMoreInteractions;
  import static org.mockito.Mockito.when;
  import static org.mockito.Mockito.withSettings;
  
  import com.google.common.collect.ImmutableMap;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import org.junit.jupiter.api.Tag;
  import org.junit.jupiter.api.Test;
  import org.mockito.InOrder;
  import org.mockito.Mockito;
  import org.openqa.selenium.Alert;
  import org.openqa.selenium.By;
  import org.openqa.selenium.Capabilities;
  import org.openqa.selenium.HasCapabilities;
  import org.openqa.selenium.ImmutableCapabilities;
  import org.openqa.selenium.JavascriptExecutor;
  import org.openqa.selenium.NoSuchElementException;
  import org.openqa.selenium.OutputType;
  import org.openqa.selenium.StubDriver;
  import org.openqa.selenium.TakesScreenshot;
  import org.openqa.selenium.WebDriver;
  import org.openqa.selenium.WebDriver.Navigation;
  import org.openqa.selenium.WebDriver.TargetLocator;
  import org.openqa.selenium.WebElement;
  import org.openqa.selenium.WrapsDriver;
  import org.openqa.selenium.WrapsElement;
  
  @Tag("UnitTests")
  class EventFiringWebDriverTest {
  
    @Test
    void alertEvents() {
      final WebDriver mockedDriver = mock(WebDriver.class);
      final Alert mockedAlert = mock(Alert.class);
      final WebDriver.TargetLocator mockedTargetLocator = mock(WebDriver.TargetLocator.class);
  
      when(mockedDriver.switchTo()).thenReturn(mockedTargetLocator);
      when(mockedTargetLocator.alert()).thenReturn(mockedAlert);
  
      WebDriverEventListener listener = mock(WebDriverEventListener.class);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver).register(listener);
  
      testedDriver.switchTo().alert().accept();
      testedDriver.switchTo().alert().dismiss();
  
      InOrder order = Mockito.inOrder(mockedDriver, mockedAlert, listener);
      order.verify(mockedDriver).switchTo();
      order.verify(listener).beforeAlertAccept(any(WebDriver.class));
      order.verify(mockedAlert).accept();
      order.verify(listener).afterAlertAccept(any(WebDriver.class));
      order.verify(mockedDriver).switchTo();
      order.verify(listener).beforeAlertDismiss(any(WebDriver.class));
      order.verify(mockedAlert).dismiss();
      order.verify(listener).afterAlertDismiss(any(WebDriver.class));
      verifyNoMoreInteractions(mockedDriver, mockedAlert, listener);
    }
  
    @Test
    void navigationEvents() {
      final WebDriver mockedDriver = mock(WebDriver.class);
      final Navigation mockedNavigation = mock(Navigation.class);
  
      when(mockedDriver.navigate()).thenReturn(mockedNavigation);
  
      WebDriverEventListener listener = mock(WebDriverEventListener.class);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver).register(listener);
  
      testedDriver.get("http://www.get.com");
      testedDriver.navigate().to("http://www.navigate-to.com");
      testedDriver.navigate().back();
      testedDriver.navigate().forward();
      testedDriver.navigate().refresh();
  
      InOrder order = Mockito.inOrder(mockedDriver, mockedNavigation, listener);
      order.verify(listener).beforeNavigateTo(eq("http://www.get.com"), any(WebDriver.class));
      order.verify(mockedDriver).get("http://www.get.com");
      order.verify(listener).afterNavigateTo(eq("http://www.get.com"), any(WebDriver.class));
      order.verify(mockedDriver).navigate();
      order.verify(listener).beforeNavigateTo(eq("http://www.navigate-to.com"), any(WebDriver.class));
      order.verify(mockedNavigation).to("http://www.navigate-to.com");
      order.verify(listener).afterNavigateTo(eq("http://www.navigate-to.com"), any(WebDriver.class));
      order.verify(mockedDriver).navigate();
      order.verify(listener).beforeNavigateBack(any(WebDriver.class));
      order.verify(mockedNavigation).back();
      order.verify(listener).afterNavigateBack(any(WebDriver.class));
      order.verify(mockedDriver).navigate();
      order.verify(listener).beforeNavigateForward(any(WebDriver.class));
      order.verify(mockedNavigation).forward();
      order.verify(listener).afterNavigateForward(any(WebDriver.class));
      order.verify(mockedDriver).navigate();
      order.verify(listener).beforeNavigateRefresh(any(WebDriver.class));
      order.verify(mockedNavigation).refresh();
      order.verify(listener).afterNavigateRefresh(any(WebDriver.class));
      verifyNoMoreInteractions(mockedDriver, mockedNavigation, listener);
    }
  
    @Test
    void clickEvent() {
      final WebDriver mockedDriver = mock(WebDriver.class);
      final WebElement mockedElement = mock(WebElement.class);
  
      when(mockedDriver.findElement(By.name("foo"))).thenReturn(mockedElement);
  
      WebDriverEventListener listener = mock(WebDriverEventListener.class);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver).register(listener);
  
      testedDriver.findElement(By.name("foo")).click();
  
      InOrder order = Mockito.inOrder(mockedDriver, mockedElement, listener);
      order.verify(listener).beforeFindBy(eq(By.name("foo")), eq(null), any(WebDriver.class));
      order.verify(mockedDriver).findElement(By.name("foo"));
      order.verify(listener).afterFindBy(eq(By.name("foo")), eq(mockedElement), any(WebDriver.class));
      order.verify(listener).beforeClickOn(any(WebElement.class), any(WebDriver.class));
      order.verify(mockedElement).click();
      order.verify(listener).afterClickOn(any(WebElement.class), any(WebDriver.class));
      verifyNoMoreInteractions(mockedDriver, mockedElement, listener);
    }
  
    @Test
    void windowEvent() {
      String windowName = "Window name";
      WebDriver mockedDriver = mock(WebDriver.class);
      TargetLocator mockedTargetLocator = mock(TargetLocator.class);
      WebDriverEventListener listener = mock(WebDriverEventListener.class);
  
      when(mockedDriver.switchTo()).thenReturn(mockedTargetLocator);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver).register(listener);
  
      testedDriver.switchTo().window(windowName);
  
      InOrder order = Mockito.inOrder(mockedTargetLocator, listener);
      order.verify(listener).beforeSwitchToWindow(eq(windowName), any(WebDriver.class));
      order.verify(mockedTargetLocator).window(windowName);
      order.verify(listener).afterSwitchToWindow(eq(windowName), any(WebDriver.class));
      verifyNoMoreInteractions(mockedTargetLocator, listener);
    }
  
    @Test
    void changeValueEvent() {
      final WebDriver mockedDriver = mock(WebDriver.class);
      final WebElement mockedElement = mock(WebElement.class);
  
      when(mockedDriver.findElement(By.name("foo"))).thenReturn(mockedElement);
  
      WebDriverEventListener listener = mock(WebDriverEventListener.class);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver).register(listener);
  
      String someText = "some text";
  
      testedDriver.findElement(By.name("foo")).clear();
      testedDriver.findElement(By.name("foo")).sendKeys(someText);
  
      InOrder order = Mockito.inOrder(mockedElement, listener);
      order
          .verify(listener)
          .beforeChangeValueOf(any(WebElement.class), any(WebDriver.class), eq(null));
      order.verify(mockedElement).clear();
      order
          .verify(listener)
          .afterChangeValueOf(any(WebElement.class), any(WebDriver.class), eq(null));
      order
          .verify(listener)
          .beforeChangeValueOf(
              any(WebElement.class), any(WebDriver.class), eq(new CharSequence[] {someText}));
      order.verify(mockedElement).sendKeys(someText);
      order
          .verify(listener)
          .afterChangeValueOf(
              any(WebElement.class), any(WebDriver.class), eq(new CharSequence[] {someText}));
  
      verify(mockedDriver, times(2)).findElement(By.name("foo"));
      verify(listener, times(2)).beforeFindBy(eq(By.name("foo")), eq(null), any(WebDriver.class));
      verify(listener, times(2))
          .afterFindBy(eq(By.name("foo")), eq(mockedElement), any(WebDriver.class));
      verifyNoMoreInteractions(mockedDriver, mockedElement, listener);
    }
  
    @Test
    void findByEvent() {
      final WebDriver mockedDriver = mock(WebDriver.class);
      final WebElement mockedElement = mock(WebElement.class);
      final WebElement mockedChildElement = mock(WebElement.class);
  
      when(mockedDriver.findElement(By.id("foo"))).thenReturn(mockedElement);
      when(mockedElement.findElement(any())).thenReturn(mockedChildElement);
  
      WebDriverEventListener listener = mock(WebDriverEventListener.class);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver).register(listener);
  
      WebElement element = testedDriver.findElement(By.id("foo"));
      element.findElement(By.linkText("bar"));
      element.findElements(By.name("xyz"));
      testedDriver.findElements(By.xpath("//link[@type = 'text/css']"));
  
      InOrder order = Mockito.inOrder(mockedElement, mockedDriver, listener);
      verify(listener).beforeFindBy(eq(By.id("foo")), eq(null), any(WebDriver.class));
      order.verify(mockedDriver).findElement(By.id("foo"));
      verify(listener).afterFindBy(eq(By.id("foo")), eq(mockedElement), any(WebDriver.class));
      verify(listener)
          .beforeFindBy(eq(By.linkText("bar")), any(WebElement.class), any(WebDriver.class));
      order.verify(mockedElement).findElement(By.linkText("bar"));
      verify(listener)
          .afterFindBy(eq(By.linkText("bar")), any(WebElement.class), any(WebDriver.class));
      verify(listener).beforeFindBy(eq(By.name("xyz")), any(WebElement.class), any(WebDriver.class));
      order.verify(mockedElement).findElements(By.name("xyz"));
      verify(listener).afterFindBy(eq(By.name("xyz")), any(WebElement.class), any(WebDriver.class));
      verify(listener)
          .beforeFindBy(eq(By.xpath("//link[@type = 'text/css']")), eq(null), any(WebDriver.class));
      order.verify(mockedDriver).findElements(By.xpath("//link[@type = 'text/css']"));
      verify(listener)
          .afterFindBy(eq(By.xpath("//link[@type = 'text/css']")), eq(null), any(WebDriver.class));
      verifyNoMoreInteractions(mockedElement, mockedDriver, listener);
    }
  
    @Test
    void shouldCallListenersWhenAnExceptionIsThrown() {
      final WebDriver mockedDriver = mock(WebDriver.class);
  
      final NoSuchElementException exception = new NoSuchElementException("argh");
  
      when(mockedDriver.findElement(By.id("foo"))).thenThrow(exception);
  
      WebDriverEventListener listener = mock(WebDriverEventListener.class);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver).register(listener);
  
      assertThatExceptionOfType(NoSuchElementException.class)
          .isThrownBy(() -> testedDriver.findElement(By.id("foo")));
  
      InOrder order = Mockito.inOrder(mockedDriver, listener);
      order.verify(listener).beforeFindBy(eq(By.id("foo")), eq(null), any(WebDriver.class));
      order.verify(mockedDriver).findElement(By.id("foo"));
      order.verify(listener).onException(any(NoSuchElementException.class), any(WebDriver.class));
      verifyNoMoreInteractions(mockedDriver, listener);
    }
  
    @Test
    void shouldUnpackElementArgsWhenCallingScripts() {
      final WebDriver mockedDriver =
          mock(WebDriver.class, withSettings().extraInterfaces(JavascriptExecutor.class));
      final WebElement stubbedElement = mock(WebElement.class);
  
      when(mockedDriver.findElement(By.id("foo"))).thenReturn(stubbedElement);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver);
      testedDriver.register(new AbstractWebDriverEventListener() {});
  
      WebElement element = testedDriver.findElement(By.id("foo"));
      testedDriver.executeScript("foo", element);
      verify((JavascriptExecutor) mockedDriver).executeScript("foo", element);
    }
  
    @Test
    void shouldWrapElementFoundWhenCallingScripts() {
      final WebDriver mockedDriver =
          mock(WebDriver.class, withSettings().extraInterfaces(JavascriptExecutor.class));
      final WebElement stubbedElement = mock(WebElement.class);
  
      when(((JavascriptExecutor) mockedDriver).executeScript("foo")).thenReturn(stubbedElement);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver);
  
      Object res = testedDriver.executeScript("foo");
      verify((JavascriptExecutor) mockedDriver).executeScript("foo");
      assertThat(res).isInstanceOf(WebElement.class).isInstanceOf(WrapsElement.class);
      assertThat(((WrapsElement) res).getWrappedElement()).isSameAs(stubbedElement);
    }
  
    @Test
    void testShouldUnpackListOfElementArgsWhenCallingScripts() {
      final WebDriver mockedDriver =
          mock(WebDriver.class, withSettings().extraInterfaces(JavascriptExecutor.class));
      final WebElement mockElement = mock(WebElement.class);
  
      when(mockedDriver.findElement(By.id("foo"))).thenReturn(mockElement);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver);
      testedDriver.register(new AbstractWebDriverEventListener() {});
  
      final WebElement foundElement = testedDriver.findElement(By.id("foo"));
      assertThat(foundElement).isInstanceOf(WrapsElement.class);
      assertThat(((WrapsElement) foundElement).getWrappedElement()).isSameAs(mockElement);
  
      List<Object> args = Arrays.asList("before", foundElement, "after");
  
      testedDriver.executeScript("foo", args);
  
      verify((JavascriptExecutor) mockedDriver).executeScript("foo", args);
    }
  
    @Test
    void shouldWrapMultipleElementsFoundWhenCallingScripts() {
      final WebDriver mockedDriver =
          mock(WebDriver.class, withSettings().extraInterfaces(JavascriptExecutor.class));
      final WebElement stubbedElement1 = mock(WebElement.class);
      final WebElement stubbedElement2 = mock(WebElement.class);
  
      when(((JavascriptExecutor) mockedDriver).executeScript("foo"))
          .thenReturn(Arrays.asList(stubbedElement1, stubbedElement2));
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver);
  
      Object res = testedDriver.executeScript("foo");
      verify((JavascriptExecutor) mockedDriver).executeScript("foo");
      assertThat(res).isInstanceOf(List.class);
      List<Object> resList = (List<Object>) res;
      resList.forEach(el -> assertThat(el).isInstanceOf(WrapsElement.class));
      assertThat(((WrapsElement) resList.get(0)).getWrappedElement()).isSameAs(stubbedElement1);
      assertThat(((WrapsElement) resList.get(1)).getWrappedElement()).isSameAs(stubbedElement2);
    }
  
    @Test
    void shouldWrapMapsWithNullValues() {
      Map<String, Object> map = new HashMap<>();
      map.put("a", null);
      final WebDriver mockedDriver =
          mock(WebDriver.class, withSettings().extraInterfaces(JavascriptExecutor.class));
      when(((JavascriptExecutor) mockedDriver).executeScript("foo")).thenReturn(map);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver);
  
      Object res = testedDriver.executeScript("foo");
      verify((JavascriptExecutor) mockedDriver).executeScript("foo");
      assertThat(res).isInstanceOf(Map.class);
      assertThat(((Map<String, Object>) res).get("a")).isNull();
    }
  
    @Test
    void testShouldUnpackMapOfElementArgsWhenCallingScripts() {
      final WebDriver mockedDriver =
          mock(WebDriver.class, withSettings().extraInterfaces(JavascriptExecutor.class));
      final WebElement mockElement = mock(WebElement.class);
  
      when(mockedDriver.findElement(By.id("foo"))).thenReturn(mockElement);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver);
      testedDriver.register(mock(WebDriverEventListener.class));
  
      final WebElement foundElement = testedDriver.findElement(By.id("foo"));
      assertThat(foundElement).isInstanceOf(WrapsElement.class);
      assertThat(((WrapsElement) foundElement).getWrappedElement()).isSameAs(mockElement);
  
      ImmutableMap<String, Object> args =
          ImmutableMap.of(
              "foo",
              "bar",
              "element",
              foundElement,
              "nested",
              Arrays.asList("before", foundElement, "after"));
  
      testedDriver.executeScript("foo", args);
  
      verify((JavascriptExecutor) mockedDriver).executeScript("foo", args);
    }
  
    @Test
    void shouldBeAbleToWrapSubclassesOfSomethingImplementingTheWebDriverInterface() {
      new EventFiringWebDriver(new ChildDriver());
      // We should get this far
    }
  
    @Test
    void shouldBeAbleToAccessWrappedInstanceFromEventCalls() {
      final WebDriver stub = mock(WebDriver.class);
      EventFiringWebDriver driver = new EventFiringWebDriver(stub);
      WebDriver wrapped = driver.getWrappedDriver();
      assertThat(wrapped).isEqualTo(stub);
  
      class MyListener extends AbstractWebDriverEventListener {
        @Override
        public void beforeNavigateTo(String url, WebDriver driver) {
          WebDriver unwrapped = ((WrapsDriver) driver).getWrappedDriver();
  
          assertThat(unwrapped).isEqualTo(stub);
        }
      }
  
      driver.register(new MyListener());
  
      driver.get("http://example.org");
    }
  
    @Test
    void shouldBeAbleToAccessWrappedElementInstanceFromEventCalls() {
      final WebElement stubElement = mock(WebElement.class);
  
      final WebDriver stubDriver = mock(WebDriver.class);
      when(stubDriver.findElement(By.name("stub"))).thenReturn(stubElement);
  
      EventFiringWebDriver driver = new EventFiringWebDriver(stubDriver);
  
      class MyListener extends AbstractWebDriverEventListener {
        @Override
        public void beforeClickOn(WebElement element, WebDriver driver) {
          assertThat(((WrapsElement) element).getWrappedElement()).isEqualTo(stubElement);
        }
      }
  
      driver.register(new MyListener());
  
      driver.findElement(By.name("stub")).click();
    }
  
    @Test
    void shouldReturnLocatorFromToStringMethod() {
      final WebElement stubElement = mock(WebElement.class);
      when(stubElement.toString()).thenReturn("cheese");
  
      final WebDriver driver = mock(WebDriver.class);
      when(driver.findElement(By.id("ignored"))).thenReturn(stubElement);
  
      EventFiringWebDriver firingDriver = new EventFiringWebDriver(driver);
      WebElement firingElement = firingDriver.findElement(By.id("ignored"));
  
      assertThat(firingElement.toString()).isEqualTo(stubElement.toString());
    }
  
    private static class ChildDriver extends StubDriver {}
  
    @Test
    void getScreenshotAs() {
      final String DATA = "data";
      WebDriver mockedDriver =
          mock(WebDriver.class, withSettings().extraInterfaces(TakesScreenshot.class));
      WebDriverEventListener listener = mock(WebDriverEventListener.class);
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver).register(listener);
  
      doReturn(DATA).when((TakesScreenshot) mockedDriver).getScreenshotAs(OutputType.BASE64);
  
      String screenshot = ((TakesScreenshot) testedDriver).getScreenshotAs(OutputType.BASE64);
      assertThat(screenshot).isEqualTo(DATA);
  
      InOrder order = Mockito.inOrder(mockedDriver, listener);
      order.verify(listener).beforeGetScreenshotAs(OutputType.BASE64);
      order.verify((TakesScreenshot) mockedDriver).getScreenshotAs(OutputType.BASE64);
      order.verify(listener).afterGetScreenshotAs(OutputType.BASE64, screenshot);
      verifyNoMoreInteractions(mockedDriver, listener);
    }
  
    @Test
    void shouldFireEventsAroundGetText() {
      final String SAMPLE = "Sample text";
      final WebDriver mockedDriver = mock(WebDriver.class);
      final WebElement mockedElement = mock(WebElement.class);
  
      when(mockedDriver.findElement(By.name("foo"))).thenReturn(mockedElement);
      when(mockedElement.getText()).thenReturn(SAMPLE);
  
      WebDriverEventListener listener = mock(WebDriverEventListener.class);
  
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver).register(listener);
  
      String text = testedDriver.findElement(By.name("foo")).getText();
      assertThat(text).isEqualTo(SAMPLE);
  
      InOrder order = Mockito.inOrder(mockedDriver, mockedElement, listener);
      order.verify(listener).beforeFindBy(eq(By.name("foo")), eq(null), any(WebDriver.class));
      order.verify(mockedDriver).findElement(By.name("foo"));
      order.verify(listener).afterFindBy(eq(By.name("foo")), eq(mockedElement), any(WebDriver.class));
      order.verify(listener).beforeGetText(any(WebElement.class), any(WebDriver.class));
      order.verify(mockedElement).getText();
      order.verify(listener).afterGetText(any(WebElement.class), any(WebDriver.class), eq(text));
      verifyNoMoreInteractions(mockedDriver, mockedElement, listener);
    }
  
    @Test
    void shouldReturnCapabilitiesWhenUnderlyingDriverImplementsInterface() {
      WebDriver mockedDriver =
          mock(WebDriver.class, withSettings().extraInterfaces(HasCapabilities.class));
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver);
  
      final Capabilities caps = new ImmutableCapabilities();
      when(((HasCapabilities) mockedDriver).getCapabilities()).thenReturn(caps);
  
      assertThat(testedDriver.getCapabilities()).isSameAs(caps);
    }
  
    @Test
    void shouldThrowExceptionWhenUnderlyingDriverDoesNotImplementInterface() {
      WebDriver mockedDriver = mock(WebDriver.class);
      EventFiringWebDriver testedDriver = new EventFiringWebDriver(mockedDriver);
  
      assertThatExceptionOfType(UnsupportedOperationException.class)
          .isThrownBy(testedDriver::getCapabilities)
          .withMessage("Underlying driver does not implement getting capabilities yet.");
    }
  }
  