diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c9b6a5a0..598effd4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -48,4 +48,4 @@ updates: maven-low-risk: update-types: - "minor" - - "patch" + - "patch" \ No newline at end of file diff --git a/selenium/src/main/java/com/deque/html/axecore/extensions/WebDriverExtensions.java b/selenium/src/main/java/com/deque/html/axecore/extensions/WebDriverExtensions.java index 9c680ae2..b7b80f62 100644 --- a/selenium/src/main/java/com/deque/html/axecore/extensions/WebDriverExtensions.java +++ b/selenium/src/main/java/com/deque/html/axecore/extensions/WebDriverExtensions.java @@ -17,6 +17,7 @@ import com.deque.html.axecore.selenium.AxeBuilderOptions; import java.io.IOException; import java.util.ArrayList; +import java.util.Set; import javax.naming.OperationNotSupportedException; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; @@ -122,10 +123,26 @@ public static String openAboutBlank(final WebDriver webDriver) { try { JavascriptExecutor driver = (JavascriptExecutor) webDriver; + Set beforeHandles = webDriver.getWindowHandles(); driver.executeScript("window.open('about:blank', '_blank')"); - ArrayList handles = new ArrayList(webDriver.getWindowHandles()); - String abHandle = handles.get(handles.size() - 1); - webDriver.switchTo().window(abHandle); + Set afterHandles = webDriver.getWindowHandles(); + + // Note: this is a work around for handling opening about:blank within the Safari driver. + // As we need to support Selenium 3 and 4, we cannot use the new window API. + // However, we compare the handles before and after opening about:blank and find the new + // handle. + // This is not ideal, but it is the best we can do for now. + // TODO: Remove this workaround if/when we drop support for Selenium 3 + // https://github.com/dequelabs/axe-core-maven-html/issues/411 + ArrayList newHandles = new ArrayList<>(afterHandles); + newHandles.removeAll(beforeHandles); + + if (newHandles.size() != 1) { + throw new RuntimeException("Unable to determine window handle"); + } + + String aboutBlankHandle = newHandles.get(0); + webDriver.switchTo().window(aboutBlankHandle); webDriver.get("about:blank"); } catch (Exception e) { throw new RuntimeException( diff --git a/selenium/src/main/java/com/deque/html/axecore/selenium/AxeBuilder.java b/selenium/src/main/java/com/deque/html/axecore/selenium/AxeBuilder.java index a0dddc3e..9d33be5e 100644 --- a/selenium/src/main/java/com/deque/html/axecore/selenium/AxeBuilder.java +++ b/selenium/src/main/java/com/deque/html/axecore/selenium/AxeBuilder.java @@ -31,10 +31,6 @@ import java.util.Map; import java.util.Stack; import java.util.StringJoiner; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import org.openqa.selenium.InvalidArgumentException; import org.openqa.selenium.JavascriptException; @@ -863,27 +859,7 @@ private Results analyzePre43x(final WebDriver webDriver, final Object rawContext return results; } - private void assertFrameReady(final WebDriver webDriver) { - // Wait so that we know there is an execution context. - // Assume that if we have an html node we have an execution context. - try { - boolean ready = - CompletableFuture.supplyAsync( - () -> - (boolean) - WebDriverInjectorExtensions.executeScript( - webDriver, "return document.readyState === 'complete'")) - .get(FRAME_LOAD_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS); - if (!ready) { - throw new RuntimeException("Page/frame is not ready"); - } - } catch (TimeoutException | InterruptedException | ExecutionException e) { - throw new RuntimeException("Page/frame is not ready"); - } - } - private void injectAxe(final WebDriver webDriver) { - assertFrameReady(webDriver); if (!doNotInjectAxe) { try { WebDriverInjectorExtensions.executeScript( diff --git a/selenium/src/test/java/com/deque/html/axecore/selenium/WebDriverExtensionsTest.java b/selenium/src/test/java/com/deque/html/axecore/selenium/WebDriverExtensionsTest.java new file mode 100644 index 00000000..075069b7 --- /dev/null +++ b/selenium/src/test/java/com/deque/html/axecore/selenium/WebDriverExtensionsTest.java @@ -0,0 +1,155 @@ +package com.deque.html.axecore.selenium; + +import com.deque.html.axecore.extensions.WebDriverExtensions; +import java.util.ArrayList; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Test; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.firefox.FirefoxOptions; +import org.openqa.selenium.safari.SafariDriver; + +public class WebDriverExtensionsTest { + private enum Browser { + CHROME, + FIREFOX, + SAFARI + } + + private ArrayList tryOpenAboutBlank(Browser browser) { + WebDriver webDriver = null; + switch (browser) { + case CHROME: + webDriver = new ChromeDriver(new ChromeOptions().addArguments("--headless")); + break; + case FIREFOX: + webDriver = new FirefoxDriver(new FirefoxOptions().addArguments("--headless")); + break; + case SAFARI: + // SafariDriver does not support headless mode + webDriver = new SafariDriver(); + break; + } + + Exception exception = null; + + String addr = "http://localhost:8001"; + webDriver.get(addr + "/index.html"); + + try { + WebDriverExtensions.openAboutBlank(webDriver); + } catch (Exception e) { + exception = e; + } + + // store exception and the current url in the webDriver to be checked later + ArrayList exceptionAndUrl = new ArrayList<>(); + + exceptionAndUrl.add(exception); + exceptionAndUrl.add(webDriver.getCurrentUrl()); + + webDriver.quit(); + + return exceptionAndUrl; + } + + @Test + public void shouldNotThrowGivenChromedriver() { + ArrayList exceptionAndUrl = tryOpenAboutBlank(Browser.CHROME); + + Exception exception = (Exception) exceptionAndUrl.get(0); + String url = (String) exceptionAndUrl.get(1); + + Assert.assertNull(exception); + Assert.assertEquals(url, "about:blank"); + } + + @Test + public void shouldNotThrowGivenGeckodriver() { + ArrayList exceptionAndUrl = tryOpenAboutBlank(Browser.FIREFOX); + + Exception exception = (Exception) exceptionAndUrl.get(0); + String url = (String) exceptionAndUrl.get(1); + + Assert.assertNull(exception); + Assert.assertEquals(url, "about:blank"); + } + + @Test + public void shouldNotThrowGivenSafariDriver() { + // if OS is windows or linux, skip this test as Safari is not available + String os = System.getProperty("os.name").toLowerCase(); + Assume.assumeFalse(os.contains("windows")); + Assume.assumeFalse(os.contains("linux")); + + ArrayList exceptionAndUrl = tryOpenAboutBlank(Browser.SAFARI); + + Exception exception = (Exception) exceptionAndUrl.get(0); + String url = (String) exceptionAndUrl.get(1); + + Assert.assertNull(exception); + Assert.assertEquals(url, "about:blank"); + } + + @Test + public void shouldThrowWhenSwitchToFails() { + // Create a mock driver to throw an exception when switchTo() is called + // This is to simulate `switchTo()` failing and throwing an exception + // We expect the exception to be caught and handled correctly. + class MockedDriver extends ChromeDriver { + public MockedDriver(ChromeOptions chromeOptions) { + super(chromeOptions); + } + + @Override + public WebDriver.TargetLocator switchTo() { + throw new RuntimeException("BOOM!"); + } + } + + MockedDriver webDriver = new MockedDriver(new ChromeOptions().addArguments("--headless")); + webDriver.get("http://localhost:8001/index.html"); + + Exception exception = + Assert.assertThrows( + Exception.class, + () -> { + WebDriverExtensions.openAboutBlank(webDriver); + }); + + Assert.assertTrue(exception.getMessage().contains("switchToWindow failed.")); + } + + @Test + public void shouldThrowWhenUnableToDetermineWindowHandle() { + class MockedDriver extends ChromeDriver { + public MockedDriver(ChromeOptions chromeOptions) { + super(chromeOptions); + } + + @Override + public Object executeScript(String script, Object... args) { + // Note: This is to simulate another window being created along with the about:blank + // window. This is to simulate the case where the about:blank window is not the + // only window being created and the window handle cannot be determined. + super.executeScript(script, args); + return super.executeScript(script, args); + } + } + + MockedDriver webDriver = new MockedDriver(new ChromeOptions().addArguments("--headless")); + webDriver.get("http://localhost:8001/index.html"); + + RuntimeException exception = + Assert.assertThrows( + RuntimeException.class, + () -> { + WebDriverExtensions.openAboutBlank(webDriver); + }); + + Assert.assertEquals(exception.getCause().getMessage(), "Unable to determine window handle"); + } +}