Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(selenium): apply Selenium 3 and 4 workaround for switchTo() when using Safari Driver #412

Merged
merged 4 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ updates:
maven-low-risk:
update-types:
- "minor"
- "patch"
- "patch"
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -122,10 +123,26 @@ public static String openAboutBlank(final WebDriver webDriver) {

try {
JavascriptExecutor driver = (JavascriptExecutor) webDriver;
Set<String> beforeHandles = webDriver.getWindowHandles();
driver.executeScript("window.open('about:blank', '_blank')");
ArrayList<String> handles = new ArrayList<String>(webDriver.getWindowHandles());
String abHandle = handles.get(handles.size() - 1);
webDriver.switchTo().window(abHandle);
Set<String> 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<String> 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed. This is already handled by the driver's get method as per the W3C spec. This is causing unwanted side effects using the Safari driver

if (!doNotInjectAxe) {
try {
WebDriverInjectorExtensions.executeScript(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Object> 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<Object> exceptionAndUrl = new ArrayList<>();

exceptionAndUrl.add(exception);
exceptionAndUrl.add(webDriver.getCurrentUrl());

webDriver.quit();

return exceptionAndUrl;
}

@Test
public void shouldNotThrowGivenChromedriver() {
ArrayList<Object> 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<Object> 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<Object> 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);
Zidious marked this conversation as resolved.
Show resolved Hide resolved
}
}

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");
}
}
Loading