package jp.vmi.selenium.selenese.utils;
import java.util.Set;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchWindowException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import jp.vmi.selenium.selenese.Context;
import jp.vmi.selenium.webdriver.WebDriverManager;
/**
* Re-implementation of "com.thoughtworks.selenium.webdriven.Windows".
*/
public class WindowSelector {
private static final String NO_PREFIX = "";
private static final long WAIT_AFTER_SELECTING_WINDOW = 500; // ms
private static WindowSelector windowSelector = new WindowSelector();
/**
* Set WindowSelector instance.
*
* @param ws WindowSelector instance.
*/
public static void setInstance(WindowSelector ws) {
windowSelector = ws;
}
/**
* Get WindowSelector instance.
*
* @return windowSelector WindowSelector instance.
*/
public static WindowSelector getInstance() {
return windowSelector;
}
private boolean isNullWindowID(String windowID) {
return windowID == null || windowID.isEmpty() || "null".equals(windowID);
}
private String getWindowHandle(WebDriver driver) {
try {
return driver.getWindowHandle();
} catch (NoSuchWindowException e) {
return null;
}
}
private String switchToWindow(WebDriver driver, String nameOrHandle) {
try {
driver.switchTo().window(nameOrHandle);
return nameOrHandle;
} catch (NoSuchWindowException e) {
return null;
}
}
/**
* Select window.
*
* @param context Selenese Runner context.
* @param windowID window ID.
* @return selected window handle or null.
*
* <p>
* The following text is copied from:<br>
* https://github.com/SeleniumHQ/selenium/blob/master/ide/main/src/content/selenium-core/reference.html<br>
* (Note: "<strong>var=</strong>" is not supported with the restrictions of Selenium WebDriver)
* </p>
* <p>
* Selects a popup window using a window locator; once a popup window has
* been selected, all commands go to that window. To select the main
* window again, use null as the target.
* </p>
*
* <p>
* Window locators provide different ways of specifying the window
* object: by title, by internal JavaScript "name," or by JavaScript
* variable.
* </p>
*
* <dl>
* <dt>title=My Special Window</dt>
* <dd>
* Finds the window using the text that appears in the title bar.
* Be careful; two windows can share the same title.
* If that happens, this locator will just pick one.
* </dd>
*
* <dt>name=myWindow</dt>
* <dd>
* Finds the window using its internal JavaScript "name" property.
* This is the second parameter "windowName" passed to the JavaScript
* method window.open(url, windowName, windowFeatures, replaceFlag)
* (which Selenium intercepts).
* </dd>
*
* <dt>var=variableName (* Selenese Runner not supported)</dt>
* <dd>
* Some pop-up windows are unnamed (anonymous), but are associated
* with a JavaScript variable name in the current application window,
* e.g. "window.foo = window.open(url);". In those cases, you can open
* the window using "var=foo".
* </dd>
* </dl>
*
* <p>
* If no window locator prefix is provided, we'll try to guess what you
* mean like this:
* </p>
*
* <ol>
* <li>
* if windowID is null, (or the string "null") then it is assumed the
* user is referring to the original window instantiated by the browser).
* </li>
* <li>
* (* Selenese Runner not supported)
* if the value of the "windowID" parameter is a JavaScript variable
* name in the current application window, then it is assumed that
* this variable contains the return value from a call to the
* JavaScript window.open() method.
* </li>
* <li>
* (* on Selenese Runner, windowID is window.name or window handle)
* Otherwise, selenium looks in a hash it maintains that maps string
* names to window "names".
* </li>
* <li>
* If that fails, we'll try looping over all of the known windows to
* try to find the appropriate "title". Since "title" is not
* necessarily unique, this may have unexpected behavior.
* ({@link #selectWindow(Context, String)} does not try looping)
* </li>
* </ol>
*
* <p>
* If you're having trouble figuring out the name of a window that you
* want to manipulate, look at the Selenium log messages which identify
* the names of windows created via window.open (and therefore
* intercepted by Selenium). You will see messages like the following for
* each window as it is opened:
* </p>
*
* <p>
* debug: window.open call intercepted; window ID (which you can use with
* selectWindow()) is "myNewWindow"
* </p>
*
* <p>
* In some cases, Selenium will be unable to intercept a call to
* window.open (if the call occurs during or before the "onLoad" event,
* for example). (This is bug SEL-339.) In those cases, you can force
* Selenium to notice the open window's name by using the Selenium
* openWindow command, using an empty (blank) url, like this:
* openWindow("", "myFunnyWindow").
* </p>
*
* <p>
* Arguments:<br>
* windowID - the JavaScript window ID of the window to select
* </p>
*/
public String selectWindow(Context context, String windowID) {
WebDriver driver = context.getWrappedDriver();
if (isNullWindowID(windowID))
return selectPreviousWindow(context);
else if ("_blank".equals(windowID))
return selectBlankWindow(context);
KeyValue wloc = KeyValue.parse(windowID, NO_PREFIX);
switch (wloc.getKey()) {
case NO_PREFIX:
String handle = switchToWindow(driver, wloc.getValue());
if (handle != null)
return handle;
// fall through
case "title":
return selectWindowWithTitle(context, wloc.getValue());
case "name":
return switchToWindow(driver, wloc.getValue());
default:
throw new UnsupportedOperationException(windowID);
}
}
/**
*
* @param context Selenese Runner context.
* @param windowID windowID.
* @return selected window handle or null.
*/
public String selectPopUp(Context context, String windowID) {
WebDriver driver = context.getWrappedDriver();
if (isNullWindowID(windowID)) {
Set<String> handles = driver.getWindowHandles();
handles.remove(context.getInitialWindowHandle());
for (String handle : handles) {
if (switchToWindow(driver, handle) != null)
return handle;
}
return null;
} else {
return selectWindow(context, windowID);
}
}
/**
* Select window without current selected.
*
* @param context Selenese Runner context.
* @return selected window handle or null.
*/
public String selectPreviousWindow(Context context) {
WebDriver driver = context.getWrappedDriver();
String currentHandle;
try {
currentHandle = getWindowHandle(driver);
} catch (WebDriverException e) {
currentHandle = null;
}
Set<String> handles = driver.getWindowHandles();
switch (handles.size()) {
case 0:
return null;
case 1:
if (currentHandle != null)
return currentHandle;
// fall through
default:
if (currentHandle != null)
handles.remove(currentHandle);
if (handles.size() >= 2)
handles.remove(context.getInitialWindowHandle());
for (String handle : handles) {
if (switchToWindow(driver, handle) != null)
return handle;
}
return null;
}
}
/**
* Select window with title.
*
* @param context Selenese Runner context.
* @param title window title.
* @return selected window handle or null.
*/
public String selectWindowWithTitle(Context context, String title) {
WebDriver driver = context.getWrappedDriver();
String currentHandle = getWindowHandle(driver);
for (String handle : driver.getWindowHandles()) {
if (switchToWindow(driver, handle) != null && title.equals(driver.getTitle()))
return handle;
}
switchToWindow(driver, currentHandle);
return null;
}
/**
* Select blank window.
*
* @param context Selenese Runner context.
* @return selected window handle or null.
*/
public String selectBlankWindow(Context context) {
WebDriver driver = context.getWrappedDriver();
String currentHandle = getWindowHandle(driver);
Set<String> handles = driver.getWindowHandles();
// the original window will never be a _blank window, so don't even look at it
// this is also important to skip, because the original/root window won't have
// a name either, so if we didn't know better we might think it's a _blank popup!
handles.remove(context.getInitialWindowHandle());
if (handles.isEmpty())
return null;
for (String handle : handles) {
if (switchToWindow(driver, handle) != null) {
String value = (String) ((JavascriptExecutor) driver).executeScript("return window.name;");
if (value == null || value.isEmpty())
return handle;
}
}
switchToWindow(driver, currentHandle);
return null;
}
/**
* Wait after selecting a window if the browser is Firefox.
*
* @param context Selenese Runner context.
*/
public static void waitAfterSelectingWindowIfNeed(Context context) {
if (WebDriverManager.FIREFOX.equals(context.getBrowserName())) {
// workaround for new FirefoxDriver.
try {
Thread.sleep(WAIT_AFTER_SELECTING_WINDOW);
} catch (InterruptedException e) {
// ignore.
}
}
}
}