Skip to content
Open
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 gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ lookfirstSardineVersion=5.13

jettyVersion=12.1.5

seleniumVersion=4.40.0
seleniumVersion=4.41.0

mockserverNettyVersion=5.15.0

Expand Down
19 changes: 4 additions & 15 deletions src/org/labkey/test/WebDriverWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.labkey.test.util.TextSearcher;
import org.labkey.test.util.TextSearcher.TextTransformers;
import org.labkey.test.util.Timer;
import org.labkey.test.util.selenium.JavascriptExecutorWrapper;
import org.labkey.test.util.selenium.ScrollUtils;
import org.labkey.test.util.selenium.WebDriverUtils;
import org.openqa.selenium.Alert;
Expand Down Expand Up @@ -539,11 +540,7 @@ public Object executeScript(@Language("JavaScript") String script, Object... arg
*/
public <T> T executeScript(@Language("JavaScript") String script, Class<T> expectedResultType, Object... arguments)
{
Object o = executeScript(script, arguments);
if (o != null && !expectedResultType.isAssignableFrom(o.getClass()))
Assert.fail("Script return wrong type. Expected '" + expectedResultType.getSimpleName() + "'. Got: " + o.getClass().getName() + ". Result: " + o);

return (T) o;
return new JavascriptExecutorWrapper(getDriver()).executeScript(script, expectedResultType, arguments);
}

/**
Expand All @@ -552,20 +549,12 @@ public <T> T executeScript(@Language("JavaScript") String script, Class<T> expec
*/
public Object executeAsyncScript(@Language("JavaScript") String script, Object... arguments)
{
script = "var callback = arguments[arguments.length - 1];\n" + // See WebDriver documentation for details on injected callback
"try {" +
script +
"} catch (error) { callback(error); }"; // ensure that the callback is invoked when an exception would otherwise prevent it
return ((JavascriptExecutor) getDriver()).executeAsyncScript(script, arguments);
return new JavascriptExecutorWrapper(getDriver()).executeAsyncScript(script, arguments);
}

public <T> T executeAsyncScript(@Language("JavaScript") String script, Class<T> expectedResultType, Object... arguments)
{
Object o = executeAsyncScript(script, arguments);
if (o != null && !expectedResultType.isAssignableFrom(o.getClass()))
Assert.fail("Script return wrong type. Expected '" + expectedResultType.getSimpleName() + "'. Got: " + o.getClass().getName() + ". Result: " + o);

return (T) o;
return new JavascriptExecutorWrapper(getDriver()).executeAsyncScript(script, expectedResultType, arguments);
}

@LogMethod(quiet = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public WebElement getComponentElement()

public Boolean isLoaded()
{
return getComponentElement().isDisplayed() &&
return WebElementUtils.checkVisibility(getComponentElement()) &&
!Locators.loadingGrid.existsIn(this) &&
!Locators.spinner.existsIn(this) &&
(Locator.tag("td").existsIn(this) ||
Expand Down
85 changes: 85 additions & 0 deletions src/org/labkey/test/util/selenium/JavascriptExecutorWrapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.labkey.test.util.selenium;

import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.Nullable;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.ScriptKey;
import org.openqa.selenium.WebDriver;

import java.util.Set;

public class JavascriptExecutorWrapper implements JavascriptExecutor
{
private final JavascriptExecutor _wrappedExecutor;

public JavascriptExecutorWrapper(WebDriver driver)
{
_wrappedExecutor = (JavascriptExecutor) driver;
}

@Override
public @Nullable Object executeScript(@Language("JavaScript") String script, @Nullable Object... args)
{
return _wrappedExecutor.executeScript(script, args);
}

@Override
public @Nullable Object executeScript(ScriptKey key, @Nullable Object... args)
{
return _wrappedExecutor.executeScript(key, args);
}

/**
* Wrapper for executing JavaScript through WebDriver and verifying return type.
* @param <T> See {@link JavascriptExecutor#executeScript(java.lang.String, java.lang.Object...)} for valid return types
*/
public <T> @Nullable T executeScript(@Language("JavaScript") String script, Class<T> expectedResultType, @Nullable Object... arguments)
{
return verifyType(expectedResultType, executeScript(script, arguments));
}

/**
* Wrapper for synchronous execution of asynchronous JavaScript. This wrapper extracts the 'callback' from the argument list
* See {@link JavascriptExecutor#executeAsyncScript(java.lang.String, java.lang.Object...)} for details
*/
@Override
public @Nullable Object executeAsyncScript(@Language("JavaScript") String script, @Nullable Object... arguments)
{
script = "var callback = arguments[arguments.length - 1];\n" + // See WebDriver documentation for details on injected callback
"try {" +
script +
"} catch (error) { callback(error); }"; // ensure that the callback is invoked when an exception would otherwise prevent it
return _wrappedExecutor.executeAsyncScript(script, arguments);
}

public <T> @Nullable T executeAsyncScript(@Language("JavaScript") String script, Class<T> expectedResultType, @Nullable Object... arguments)
{
return verifyType(expectedResultType, executeAsyncScript(script, arguments));
}

private <T> @Nullable T verifyType(Class<T> expectedResultType, @Nullable Object o)
{
if (o != null && !expectedResultType.isAssignableFrom(o.getClass()))
throw new IllegalStateException("Script return wrong type. Expected '" + expectedResultType.getName() + "'. Got: " + o.getClass().getName() + ". Result: " + o);

return (T) o;
}

@Override
public Set<ScriptKey> getPinnedScripts()
{
return _wrappedExecutor.getPinnedScripts();
}

@Override
public void unpin(ScriptKey key)
{
_wrappedExecutor.unpin(key);
}

@Override
public ScriptKey pin(@Language("JavaScript") String script)
{
return _wrappedExecutor.pin(script);
}
}
13 changes: 13 additions & 0 deletions src/org/labkey/test/util/selenium/WebDriverUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.openqa.selenium.WrapsDriver;
import org.openqa.selenium.WrapsElement;

import java.util.Objects;

public abstract class WebDriverUtils
{
/**
Expand Down Expand Up @@ -74,6 +76,17 @@ public static WebDriver extractWrappedDriver(Object peeling)
return null;
}

/**
* Extract a WebDriver instance from an arbitrarily wrapped object and the JavascriptExecutor tied to it.
*
* @param object Object that wraps a WebDriver. Typically, a Component, SearchContext, or WebElement
* @return JavascriptExecutor instance
*/
public static JavascriptExecutorWrapper getJavascriptExecutor(Object object)
{
return Objects.requireNonNull(new JavascriptExecutorWrapper(extractWrappedDriver(object)), () -> "No WebDriver found in " + object.getClass());
}

/**
* Attempts to get alert text from an {@link UnhandledAlertException}. If exception does not supply the alert text,
* attempt to get it from the alert directly (requires {@link org.openqa.selenium.UnexpectedAlertBehaviour#IGNORE}).
Expand Down
29 changes: 28 additions & 1 deletion src/org/labkey/test/util/selenium/WebElementUtils.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package org.labkey.test.util.selenium;

import org.intellij.lang.annotations.Language;
import org.labkey.test.selenium.LazyWebElement;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static org.labkey.test.Locator.NBSP;

Expand Down Expand Up @@ -93,6 +97,29 @@ public static String getTextNodeWithin(WebElement element)
*/
public static String getTextContent(WebElement element)
{
return element.getDomProperty("textContent").replace(NBSP, " ");
return Optional.ofNullable(element.getDomProperty("textContent")).map(s -> s.replace(NBSP, " ")).orElse(null);
}

/**
* Determines whether the specified element is visible. {@link WebElement#isDisplayed()} might return false if the
* element is out the viewport and scrolling is disabled due to a modal dialog.<br>
* TODO: Consider moving to {@link LazyWebElement#isDisplayed()}
*
* @param element element to inspect
* @return true if the element is visible, false otherwise
*/
public static boolean checkVisibility(WebElement element)
{
try
{
return element.isDisplayed() ||
Objects.requireNonNullElse(WebDriverUtils.getJavascriptExecutor(element)
.executeScript("return arguments[0].checkVisibility();", Boolean.class, element),
false);
}
catch (NoSuchElementException | StaleElementReferenceException e)
{
return false;
}
}
}